You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					262 lines
				
				6.8 KiB
			
		
		
			
		
	
	
					262 lines
				
				6.8 KiB
			| 
											3 years ago
										 | const util = require('./util') | ||
|  | 
 | ||
|  | module.exports = function stringify (value, replacer, space) { | ||
|  |     const stack = [] | ||
|  |     let indent = '' | ||
|  |     let propertyList | ||
|  |     let replacerFunc | ||
|  |     let gap = '' | ||
|  |     let quote | ||
|  | 
 | ||
|  |     if ( | ||
|  |         replacer != null && | ||
|  |         typeof replacer === 'object' && | ||
|  |         !Array.isArray(replacer) | ||
|  |     ) { | ||
|  |         space = replacer.space | ||
|  |         quote = replacer.quote | ||
|  |         replacer = replacer.replacer | ||
|  |     } | ||
|  | 
 | ||
|  |     if (typeof replacer === 'function') { | ||
|  |         replacerFunc = replacer | ||
|  |     } else if (Array.isArray(replacer)) { | ||
|  |         propertyList = [] | ||
|  |         for (const v of replacer) { | ||
|  |             let item | ||
|  | 
 | ||
|  |             if (typeof v === 'string') { | ||
|  |                 item = v | ||
|  |             } else if ( | ||
|  |                 typeof v === 'number' || | ||
|  |                 v instanceof String || | ||
|  |                 v instanceof Number | ||
|  |             ) { | ||
|  |                 item = String(v) | ||
|  |             } | ||
|  | 
 | ||
|  |             if (item !== undefined && propertyList.indexOf(item) < 0) { | ||
|  |                 propertyList.push(item) | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (space instanceof Number) { | ||
|  |         space = Number(space) | ||
|  |     } else if (space instanceof String) { | ||
|  |         space = String(space) | ||
|  |     } | ||
|  | 
 | ||
|  |     if (typeof space === 'number') { | ||
|  |         if (space > 0) { | ||
|  |             space = Math.min(10, Math.floor(space)) | ||
|  |             gap = '          '.substr(0, space) | ||
|  |         } | ||
|  |     } else if (typeof space === 'string') { | ||
|  |         gap = space.substr(0, 10) | ||
|  |     } | ||
|  | 
 | ||
|  |     return serializeProperty('', {'': value}) | ||
|  | 
 | ||
|  |     function serializeProperty (key, holder) { | ||
|  |         let value = holder[key] | ||
|  |         if (value != null) { | ||
|  |             if (typeof value.toJSON5 === 'function') { | ||
|  |                 value = value.toJSON5(key) | ||
|  |             } else if (typeof value.toJSON === 'function') { | ||
|  |                 value = value.toJSON(key) | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         if (replacerFunc) { | ||
|  |             value = replacerFunc.call(holder, key, value) | ||
|  |         } | ||
|  | 
 | ||
|  |         if (value instanceof Number) { | ||
|  |             value = Number(value) | ||
|  |         } else if (value instanceof String) { | ||
|  |             value = String(value) | ||
|  |         } else if (value instanceof Boolean) { | ||
|  |             value = value.valueOf() | ||
|  |         } | ||
|  | 
 | ||
|  |         switch (value) { | ||
|  |         case null: return 'null' | ||
|  |         case true: return 'true' | ||
|  |         case false: return 'false' | ||
|  |         } | ||
|  | 
 | ||
|  |         if (typeof value === 'string') { | ||
|  |             return quoteString(value, false) | ||
|  |         } | ||
|  | 
 | ||
|  |         if (typeof value === 'number') { | ||
|  |             return String(value) | ||
|  |         } | ||
|  | 
 | ||
|  |         if (typeof value === 'object') { | ||
|  |             return Array.isArray(value) ? serializeArray(value) : serializeObject(value) | ||
|  |         } | ||
|  | 
 | ||
|  |         return undefined | ||
|  |     } | ||
|  | 
 | ||
|  |     function quoteString (value) { | ||
|  |         const quotes = { | ||
|  |             "'": 0.1, | ||
|  |             '"': 0.2, | ||
|  |         } | ||
|  | 
 | ||
|  |         const replacements = { | ||
|  |             "'": "\\'", | ||
|  |             '"': '\\"', | ||
|  |             '\\': '\\\\', | ||
|  |             '\b': '\\b', | ||
|  |             '\f': '\\f', | ||
|  |             '\n': '\\n', | ||
|  |             '\r': '\\r', | ||
|  |             '\t': '\\t', | ||
|  |             '\v': '\\v', | ||
|  |             '\0': '\\0', | ||
|  |             '\u2028': '\\u2028', | ||
|  |             '\u2029': '\\u2029', | ||
|  |         } | ||
|  | 
 | ||
|  |         let product = '' | ||
|  | 
 | ||
|  |         for (let i = 0; i < value.length; i++) { | ||
|  |             const c = value[i] | ||
|  |             switch (c) { | ||
|  |             case "'": | ||
|  |             case '"': | ||
|  |                 quotes[c]++ | ||
|  |                 product += c | ||
|  |                 continue | ||
|  | 
 | ||
|  |             case '\0': | ||
|  |                 if (util.isDigit(value[i + 1])) { | ||
|  |                     product += '\\x00' | ||
|  |                     continue | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             if (replacements[c]) { | ||
|  |                 product += replacements[c] | ||
|  |                 continue | ||
|  |             } | ||
|  | 
 | ||
|  |             if (c < ' ') { | ||
|  |                 let hexString = c.charCodeAt(0).toString(16) | ||
|  |                 product += '\\x' + ('00' + hexString).substring(hexString.length) | ||
|  |                 continue | ||
|  |             } | ||
|  | 
 | ||
|  |             product += c | ||
|  |         } | ||
|  | 
 | ||
|  |         const quoteChar = quote || Object.keys(quotes).reduce((a, b) => (quotes[a] < quotes[b]) ? a : b) | ||
|  | 
 | ||
|  |         product = product.replace(new RegExp(quoteChar, 'g'), replacements[quoteChar]) | ||
|  | 
 | ||
|  |         return quoteChar + product + quoteChar | ||
|  |     } | ||
|  | 
 | ||
|  |     function serializeObject (value) { | ||
|  |         if (stack.indexOf(value) >= 0) { | ||
|  |             throw TypeError('Converting circular structure to JSON5') | ||
|  |         } | ||
|  | 
 | ||
|  |         stack.push(value) | ||
|  | 
 | ||
|  |         let stepback = indent | ||
|  |         indent = indent + gap | ||
|  | 
 | ||
|  |         let keys = propertyList || Object.keys(value) | ||
|  |         let partial = [] | ||
|  |         for (const key of keys) { | ||
|  |             const propertyString = serializeProperty(key, value) | ||
|  |             if (propertyString !== undefined) { | ||
|  |                 let member = serializeKey(key) + ':' | ||
|  |                 if (gap !== '') { | ||
|  |                     member += ' ' | ||
|  |                 } | ||
|  |                 member += propertyString | ||
|  |                 partial.push(member) | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         let final | ||
|  |         if (partial.length === 0) { | ||
|  |             final = '{}' | ||
|  |         } else { | ||
|  |             let properties | ||
|  |             if (gap === '') { | ||
|  |                 properties = partial.join(',') | ||
|  |                 final = '{' + properties + '}' | ||
|  |             } else { | ||
|  |                 let separator = ',\n' + indent | ||
|  |                 properties = partial.join(separator) | ||
|  |                 final = '{\n' + indent + properties + ',\n' + stepback + '}' | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         stack.pop() | ||
|  |         indent = stepback | ||
|  |         return final | ||
|  |     } | ||
|  | 
 | ||
|  |     function serializeKey (key) { | ||
|  |         if (key.length === 0) { | ||
|  |             return quoteString(key, true) | ||
|  |         } | ||
|  | 
 | ||
|  |         const firstChar = String.fromCodePoint(key.codePointAt(0)) | ||
|  |         if (!util.isIdStartChar(firstChar)) { | ||
|  |             return quoteString(key, true) | ||
|  |         } | ||
|  | 
 | ||
|  |         for (let i = firstChar.length; i < key.length; i++) { | ||
|  |             if (!util.isIdContinueChar(String.fromCodePoint(key.codePointAt(i)))) { | ||
|  |                 return quoteString(key, true) | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return key | ||
|  |     } | ||
|  | 
 | ||
|  |     function serializeArray (value) { | ||
|  |         if (stack.indexOf(value) >= 0) { | ||
|  |             throw TypeError('Converting circular structure to JSON5') | ||
|  |         } | ||
|  | 
 | ||
|  |         stack.push(value) | ||
|  | 
 | ||
|  |         let stepback = indent | ||
|  |         indent = indent + gap | ||
|  | 
 | ||
|  |         let partial = [] | ||
|  |         for (let i = 0; i < value.length; i++) { | ||
|  |             const propertyString = serializeProperty(String(i), value) | ||
|  |             partial.push((propertyString !== undefined) ? propertyString : 'null') | ||
|  |         } | ||
|  | 
 | ||
|  |         let final | ||
|  |         if (partial.length === 0) { | ||
|  |             final = '[]' | ||
|  |         } else { | ||
|  |             if (gap === '') { | ||
|  |                 let properties = partial.join(',') | ||
|  |                 final = '[' + properties + ']' | ||
|  |             } else { | ||
|  |                 let separator = ',\n' + indent | ||
|  |                 let properties = partial.join(separator) | ||
|  |                 final = '[\n' + indent + properties + ',\n' + stepback + ']' | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         stack.pop() | ||
|  |         indent = stepback | ||
|  |         return final | ||
|  |     } | ||
|  | } |