1 |
|
2 |
|
3 | const formatNumber = require('./number').format
|
4 | const formatBigNumber = require('./bignumber/formatter').format
|
5 | const isBigNumber = require('./bignumber/isBigNumber')
|
6 |
|
7 | /**
|
8 | * Test whether value is a string
|
9 | * @param {*} value
|
10 | * @return {boolean} isString
|
11 | */
|
12 | exports.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 | */
|
21 | exports.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 | */
|
62 | exports.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 | */
|
123 | exports.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 | */
|
155 | exports.escape = function (value) {
|
156 | let text = String(value)
|
157 | text = text.replace(/&/g, '&')
|
158 | .replace(/"/g, '"')
|
159 | .replace(/'/g, ''')
|
160 | .replace(/</g, '<')
|
161 | .replace(/>/g, '>')
|
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 | */
|
176 | function 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 | */
|
198 | function 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 | }
|