@@ -4,55 +4,176 @@ import kotlinx.serialization.json.JsonArray
44import kotlinx.serialization.json.JsonElement
55import kotlinx.serialization.json.JsonObject
66import kotlinx.serialization.json.JsonPrimitive
7- import kotlin.collections.component1
8- import kotlin.collections.component2
9-
10-
11- internal fun innerEncodeToString (
12- element : JsonElement ,
13- config : Json5EncoderConfig ,
14- level : Int = 0,
15- ): String = if (element is JsonPrimitive ) {
16- if (element.isString) {
17- stringifyString(element.content, config.singleQuote)
18- } else {
19- element.content
7+
8+ internal fun stringifyKey (key : String , config : Json5EncoderConfig ): String {
9+ if (key.isEmpty() || ! config.unquotedKey) {
10+ return stringifyString(key, config.singleQuote)
11+ }
12+ if (! isIdStartChar(key[0 ])) {
13+ return stringifyString(key, config.singleQuote)
2014 }
21- } else {
22- val indent = config.indent
23- val lineSeparator = if (indent.isEmpty()) " " else " \n "
24- val keySeparator = if (indent.isEmpty()) " :" else " : "
25- val newLevel = level + 1
26- val prefixSpaces = if (indent.isEmpty()) " " else indent.repeat(newLevel)
27- val closingSpaces = if (indent.isEmpty()) " " else indent.repeat(level)
28- val postfix = if (config.trailingComma) " ," else " "
29- if (element is JsonObject ) {
30- if (element.isEmpty()) {
31- " {}"
32- } else {
33- element.entries.joinToString(" ,$lineSeparator " , postfix = postfix) { (key, value) ->
34- " ${prefixSpaces}${stringifyKey(key, config.singleQuote, config.unquotedKey)}${keySeparator}${
35- innerEncodeToString(
36- value,
37- config,
38- newLevel
39- )
40- } "
41- }.let {
42- " {$lineSeparator$it$lineSeparator$closingSpaces }"
15+ for (c in key) {
16+ if (! isIdContinueChar(c)) {
17+ return stringifyString(key, config.singleQuote)
18+ }
19+ }
20+ return key
21+ }
22+
23+ private val escapeReplacements = hashMapOf(
24+ ' \\ ' to " \\\\ " ,
25+ ' \b ' to " \\ b" ,
26+ ' \u000C ' to " \\ f" ,
27+ ' \n ' to " \\ n" ,
28+ ' \r ' to " \\ r" ,
29+ ' \t ' to " \\ t" ,
30+ ' \u000B ' to " \\ v" ,
31+ ' \u0000 ' to " \\ 0" ,
32+ ' \u2028 ' to " \\ u2028" ,
33+ ' \u2029 ' to " \\ u2029" ,
34+ )
35+
36+ // https://github.com/json5/json5/blob/b935d4a280eafa8835e6182551b63809e61243b0/lib/stringify.js#L104
37+ internal fun stringifyString (value : String , singleQuote : Boolean ): String {
38+ val wrapChar = if (singleQuote) ' \' ' else ' "'
39+ val sb = StringBuilder ()
40+ sb.append(wrapChar)
41+ value.forEachIndexed { i, c ->
42+ when {
43+ c == wrapChar -> {
44+ sb.append(" \\ $wrapChar " )
45+ }
46+
47+ c == ' \u0000 ' -> {
48+ if (isDigit(value.getOrNull(i + 1 ))) {
49+ // "\u00002" -> \x002, avoid octal ambiguity
50+ sb.append(" \\ x00" )
51+ } else {
52+ sb.append(" \\ 0" )
53+ }
54+ }
55+
56+ c in escapeReplacements -> {
57+ sb.append(escapeReplacements[c])
58+ }
59+
60+ c.code in 0 .. 0x1f -> {
61+ sb.append(" \\ x" + c.code.toString(16 ).padStart(2 , ' 0' ))
62+ }
63+
64+ else -> {
65+ sb.append(c)
4366 }
4467 }
45- } else if (element is JsonArray ) {
46- if (element.isEmpty()) {
47- " []"
48- } else {
49- element.joinToString(" ,$lineSeparator " , postfix = postfix) {
50- " ${prefixSpaces}${innerEncodeToString(it, config, newLevel)} "
51- }.let {
52- " [$lineSeparator$it$lineSeparator$closingSpaces ]"
68+ }
69+ sb.append(wrapChar)
70+ return sb.toString()
71+ }
72+
73+ internal fun stringifyPrimitive (value : JsonPrimitive , config : Json5EncoderConfig ): String = when {
74+ value.isString -> stringifyString(value.content, config.singleQuote)
75+ else -> value.content
76+ }
77+
78+ internal fun JsonObject.getByIndex (index : Int ): Map .Entry <String , JsonElement > {
79+ var i = 0
80+ forEach {
81+ if (i == index) return it
82+ i++
83+ }
84+ throw IndexOutOfBoundsException (" Index: $index , Size: $size " )
85+ }
86+
87+ private fun getIndent (config : Json5EncoderConfig , ind : Int ): String {
88+ return config.indent.repeat(ind)
89+ }
90+
91+ internal fun innerEncodeToString (element : JsonElement , config : Json5EncoderConfig ): String {
92+ val sb = StringBuilder ()
93+ val elementStack = mutableListOf (element)
94+ val indexStack = mutableListOf (0 )
95+ val indentStack = mutableListOf (0 )
96+
97+ val hasIndent = config.indent.isNotEmpty()
98+ val keySeparator = if (hasIndent) " : " else " :"
99+ fun newLine () {
100+ if (hasIndent) {
101+ sb.append(" \n " )
102+ }
103+ }
104+
105+ while (elementStack.isNotEmpty()) {
106+ val value = elementStack.pop()
107+ val idx = indexStack.pop()
108+ val ind = indentStack.pop()
109+
110+ when (value) {
111+ is JsonPrimitive -> sb.append(stringifyPrimitive(value, config))
112+ is JsonArray -> {
113+ if (idx == 0 ) {
114+ sb.append(" [" )
115+ if (value.isNotEmpty()) {
116+ newLine()
117+ }
118+ }
119+
120+ if (idx < value.size) {
121+ if (idx > 0 ) {
122+ sb.append(" ," )
123+ newLine()
124+ }
125+ if (hasIndent) sb.append(getIndent(config, ind + 1 ))
126+
127+ elementStack.add(value)
128+ indexStack.add(idx + 1 )
129+ indentStack.add(ind)
130+
131+ elementStack.add(value[idx])
132+ indexStack.add(0 )
133+ indentStack.add(ind + 1 )
134+ } else {
135+ if (value.isNotEmpty()) {
136+ if (config.trailingComma) sb.append(" ," )
137+ if (hasIndent) sb.append(" \n " ).append(getIndent(config, ind))
138+ }
139+ sb.append(" ]" )
140+ }
141+ }
142+
143+ is JsonObject -> {
144+ if (idx == 0 ) {
145+ sb.append(" {" )
146+ if (value.isNotEmpty()) {
147+ newLine()
148+ }
149+ }
150+ if (idx < value.size) {
151+ if (idx > 0 ) {
152+ sb.append(" ," )
153+ newLine()
154+ }
155+ val (k, v) = value.getByIndex(idx)
156+ if (hasIndent) sb.append(getIndent(config, ind + 1 ))
157+ sb.append(stringifyKey(k, config)).append(keySeparator)
158+
159+ elementStack.add(value)
160+ indexStack.add(idx + 1 )
161+ indentStack.add(ind)
162+
163+ elementStack.add(v)
164+ indexStack.add(0 )
165+ indentStack.add(ind + 1 )
166+ } else {
167+ if (value.isNotEmpty()) {
168+ if (config.trailingComma) sb.append(" ," )
169+ if (hasIndent) sb.append(" \n " ).append(getIndent(config, ind))
170+ }
171+ sb.append(" }" )
172+ }
53173 }
54174 }
55- } else {
56- throw IllegalArgumentException ()
57175 }
176+
177+ return sb.toString()
58178}
179+
0 commit comments