const quotes = ['"', "'"] function stringify (nbt, space = false) { const sep = ',' + (space ? ' ' : '') switch (nbt.type) { case 'byte': return nbt.value + 'b' case 'short': return nbt.value + 's' case 'int': return nbt.value.toString() case 'long': return nbt.value + 'l' case 'float': return nbt.value + 'f' case 'double': return nbt.value + 'd' case 'string': return stringifyStr(nbt.value, true) case 'list': return `[${ nbt.value.value .map((tag) => stringify({ type: nbt.value.type, value: tag }, space)) .join(sep) }]` case 'compound': return `{${Object.entries(nbt.value).map(([key, prop]) => stringifyStr(key, false) + ':' + (space ? ' ' : '') + stringify(prop) ).join(sep)}}` case 'byteArray': return `[B;${space ? ' ' : ''}${ nbt.value .map((value) => stringify({ type: 'byte', value })) .join(sep) }]` case 'intArray': return `[I;${space ? ' ' : ''}${ nbt.value .map((value) => stringify({ type: 'int', value })) .join(sep) }]` case 'longArray': return `[L;${space ? ' ' : ''}${ nbt.value .map((value) => stringify({ type: 'long', value })) .join(sep) }]` case 'bool': return nbt.value.toString() default: throw new Error('Unknown type: ' + nbt.type) } function stringifyStr (str, forceQuotes = false) { const regex = /['\\]/g if (regex.test(str) || /[\s|"]/g.test(str) || forceQuotes) { str = `'${str.replace(regex, (m) => '\\' + m)}'` } return str } } function parse (snbt) { const num = parseInt(snbt) if (!isNaN(num)) { if (snbt.endsWith('b')) { return { type: 'byte', value: num } } else if (snbt.endsWith('s')) { return { type: 'short', value: num } } else if (snbt.endsWith('l')) { return { type: 'long', value: num } } else if (snbt.endsWith('f')) { return { type: 'float', value: num } } else if (snbt.endsWith('d')) { return { type: 'double', value: num } } else { return { type: 'int', value: num } } } else if (snbt === 'true') { return true } else if (snbt === 'false') { return false } else if (snbt.startsWith('{') && snbt.endsWith('}')) { const entries = split(snbt.slice(1, -1), ',').map(e => split(e, ':')).map(([key, prop]) => [parseStr(key), parse(prop)]) const obj = {} entries.forEach(([key, prop]) => (obj[key] = prop)) return { type: 'compound', value: obj } // } else if (snbt.startsWith('[') && snbt.endsWith(']')) { } else { return { type: 'string', value: parseStr(snbt) } } // worst string parser ever function parseStr (str) { const unquotedWl = /[a-zA-Z0-9+\-.]/g // check quotes for (const quote of quotes) { if (str.startsWith(quote) && str.startsWith(quote)) { return str.replace(new RegExp(`\\\\|\\${quote}`, 'g'), (m) => m.slice(1)) } } // if (!unquotedWl.test(str)) throw new Error('Invalid character in unquoted string: ' + str) return str } function split (str, char, times = Infinity) { let quote = null let split = '' const result = [] for (let i = 0; i < str.length; i++) { if (quotes.includes(str[i]) && str[i - 1] !== '\\') { if (!quote) { quote = str[i] } else if (str[i] === quote) { quote = null } } else if (str[i] === char || !quote) { result.push(split.trim()) split = '' } else { split += str[i] } } return [...result, split] } } module.exports = { stringify, parse }