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