UNPKG

5.53 kBJavaScriptView Raw
1'use strict'
2
3const formatNumber = require('./number').format
4const formatBigNumber = require('./bignumber/formatter').format
5const isBigNumber = require('./bignumber/isBigNumber')
6
7/**
8 * Test whether value is a string
9 * @param {*} value
10 * @return {boolean} isString
11 */
12exports.isString = function (value) {
13 return typeof value === 'string'
14}
15
16/**
17 * Check if a text ends with a certain string.
18 * @param {string} text
19 * @param {string} search
20 */
21exports.endsWith = function (text, search) {
22 const start = text.length - search.length
23 const end = text.length
24 return (text.substring(start, end) === search)
25}
26
27/**
28 * Format a value of any type into a string.
29 *
30 * Usage:
31 * math.format(value)
32 * math.format(value, precision)
33 *
34 * When value is a function:
35 *
36 * - When the function has a property `syntax`, it returns this
37 * syntax description.
38 * - In other cases, a string `'function'` is returned.
39 *
40 * When `value` is an Object:
41 *
42 * - When the object contains a property `format` being a function, this
43 * function is invoked as `value.format(options)` and the result is returned.
44 * - When the object has its own `toString` method, this method is invoked
45 * and the result is returned.
46 * - In other cases the function will loop over all object properties and
47 * return JSON object notation like '{"a": 2, "b": 3}'.
48 *
49 * Example usage:
50 * math.format(2/7) // '0.2857142857142857'
51 * math.format(math.pi, 3) // '3.14'
52 * math.format(new Complex(2, 3)) // '2 + 3i'
53 * math.format('hello') // '"hello"'
54 *
55 * @param {*} value Value to be stringified
56 * @param {Object | number | Function} [options] Formatting options. See
57 * lib/utils/number:format for a
58 * description of the available
59 * options.
60 * @return {string} str
61 */
62exports.format = function (value, options) {
63 if (typeof value === 'number') {
64 return formatNumber(value, options)
65 }
66
67 if (isBigNumber(value)) {
68 return formatBigNumber(value, options)
69 }
70
71 // note: we use unsafe duck-typing here to check for Fractions, this is
72 // ok here since we're only invoking toString or concatenating its values
73 if (looksLikeFraction(value)) {
74 if (!options || options.fraction !== 'decimal') {
75 // output as ratio, like '1/3'
76 return (value.s * value.n) + '/' + value.d
77 } else {
78 // output as decimal, like '0.(3)'
79 return value.toString()
80 }
81 }
82
83 if (Array.isArray(value)) {
84 return formatArray(value, options)
85 }
86
87 if (exports.isString(value)) {
88 return '"' + value + '"'
89 }
90
91 if (typeof value === 'function') {
92 return value.syntax ? String(value.syntax) : 'function'
93 }
94
95 if (value && typeof value === 'object') {
96 if (typeof value.format === 'function') {
97 return value.format(options)
98 } else if (value && value.toString() !== {}.toString()) {
99 // this object has a non-native toString method, use that one
100 return value.toString()
101 } else {
102 const entries = []
103
104 for (const key in value) {
105 if (value.hasOwnProperty(key)) {
106 entries.push('"' + key + '": ' + exports.format(value[key], options))
107 }
108 }
109
110 return '{' + entries.join(', ') + '}'
111 }
112 }
113
114 return String(value)
115}
116
117/**
118 * Stringify a value into a string enclosed in double quotes.
119 * Unescaped double quotes and backslashes inside the value are escaped.
120 * @param {*} value
121 * @return {string}
122 */
123exports.stringify = function (value) {
124 const text = String(value)
125 let escaped = ''
126 let i = 0
127 while (i < text.length) {
128 let c = text.charAt(i)
129
130 if (c === '\\') {
131 escaped += c
132 i++
133
134 c = text.charAt(i)
135 if (c === '' || '"\\/bfnrtu'.indexOf(c) === -1) {
136 escaped += '\\' // no valid escape character -> escape it
137 }
138 escaped += c
139 } else if (c === '"') {
140 escaped += '\\"'
141 } else {
142 escaped += c
143 }
144 i++
145 }
146
147 return '"' + escaped + '"'
148}
149
150/**
151 * Escape special HTML characters
152 * @param {*} value
153 * @return {string}
154 */
155exports.escape = function (value) {
156 let text = String(value)
157 text = text.replace(/&/g, '&amp;')
158 .replace(/"/g, '&quot;')
159 .replace(/'/g, '&#39;')
160 .replace(/</g, '&lt;')
161 .replace(/>/g, '&gt;')
162
163 return text
164}
165
166/**
167 * Recursively format an n-dimensional matrix
168 * Example output: "[[1, 2], [3, 4]]"
169 * @param {Array} array
170 * @param {Object | number | Function} [options] Formatting options. See
171 * lib/utils/number:format for a
172 * description of the available
173 * options.
174 * @returns {string} str
175 */
176function formatArray (array, options) {
177 if (Array.isArray(array)) {
178 let str = '['
179 const len = array.length
180 for (let i = 0; i < len; i++) {
181 if (i !== 0) {
182 str += ', '
183 }
184 str += formatArray(array[i], options)
185 }
186 str += ']'
187 return str
188 } else {
189 return exports.format(array, options)
190 }
191}
192
193/**
194 * Check whether a value looks like a Fraction (unsafe duck-type check)
195 * @param {*} value
196 * @return {boolean}
197 */
198function looksLikeFraction (value) {
199 return (value &&
200 typeof value === 'object' &&
201 typeof value.s === 'number' &&
202 typeof value.n === 'number' &&
203 typeof value.d === 'number') || false
204}