262 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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
 | 
						|
    }
 | 
						|
}
 |