11package sjsonnet
22
3- import java .io .{ StringWriter , Writer }
3+ import java .io .StringWriter
44import java .util .regex .Pattern
5+ import upickle .core .{ArrVisitor , ObjVisitor , SimpleVisitor , Visitor }
56
6- import upickle . core .{ ArrVisitor , ObjVisitor }
7+ import scala . util . Try
78
89
910
1011class YamlRenderer (_out : StringWriter = new java.io.StringWriter (), indentArrayInObject : Boolean = false ,
11- indent : Int = 2 ) extends BaseCharRenderer (_out, indent){
12+ quoteKeys : Boolean = true , indent : Int = 2 ) extends BaseCharRenderer (_out, indent){
1213 var newlineBuffered = false
1314 var dashBuffered = false
1415 var afterKey = false
1516 private var topLevel = true
17+ private val outBuffer = _out.getBuffer
18+
19+ private val yamlKeyVisitor = new SimpleVisitor [StringWriter , StringWriter ]() {
20+ override def expectedMsg = " Expected a string key"
21+ override def visitString (s : CharSequence , index : Int ): StringWriter = {
22+ YamlRenderer .this .flushBuffer()
23+ if (quoteKeys || ! YamlRenderer .isSafeBareKey(s.toString)) {
24+ upickle.core.RenderUtils .escapeChar(null , YamlRenderer .this .elemBuilder, s, unicode = true )
25+ } else {
26+ YamlRenderer .this .appendString(s.toString)
27+ }
28+ YamlRenderer .this .flushCharBuilder()
29+ _out
30+ }
31+ }
1632
17- private val outBuffer = _out.getBuffer()
18-
19- override def flushCharBuilder () = {
33+ override def flushCharBuilder (): Unit = {
2034 elemBuilder.writeOutToIfLongerThan(_out, if (depth <= 0 || topLevel) 0 else 1000 )
2135 }
2236
23- private [this ] def appendString (s : String ) = {
37+ private [this ] def appendString (s : String ): Unit = {
2438 val len = s.length
2539 var i = 0
2640 elemBuilder.ensureLength(len)
@@ -48,20 +62,20 @@ class YamlRenderer(_out: StringWriter = new java.io.StringWriter(), indentArrayI
4862 }
4963 depth -= 1
5064 } else {
51- upickle.core.RenderUtils .escapeChar(unicodeCharBuilder , elemBuilder, s, true )
65+ upickle.core.RenderUtils .escapeChar(null , elemBuilder, s, unicode = true )
5266 }
5367 flushCharBuilder()
5468 _out
5569 }
5670
57- override def visitFloat64 (d : Double , index : Int ) = {
71+ override def visitFloat64 (d : Double , index : Int ): StringWriter = {
5872 flushBuffer()
5973 appendString(RenderUtils .renderDouble(d))
6074 flushCharBuilder()
6175 _out
6276 }
6377
64- override def flushBuffer () = {
78+ override def flushBuffer (): Unit = {
6579 if (newlineBuffered) {
6680 // drop space between colon and newline
6781 elemBuilder.writeOutToIfLongerThan(_out, 0 )
@@ -81,7 +95,7 @@ class YamlRenderer(_out: StringWriter = new java.io.StringWriter(), indentArrayI
8195 dashBuffered = false
8296 }
8397
84- override def visitArray (length : Int , index : Int ) = new ArrVisitor [StringWriter , StringWriter ] {
98+ override def visitArray (length : Int , index : Int ): ArrVisitor [ StringWriter , StringWriter ] = new ArrVisitor [StringWriter , StringWriter ] {
8599 var empty = true
86100 flushBuffer()
87101
@@ -91,19 +105,19 @@ class YamlRenderer(_out: StringWriter = new java.io.StringWriter(), indentArrayI
91105 }
92106 topLevel = false
93107
94- val dedentInObject = afterKey && ! indentArrayInObject
108+ private val dedentInObject = afterKey && ! indentArrayInObject
95109 afterKey = false
96110 if (dedentInObject) depth -= 1
97111 dashBuffered = true
98112
99- def subVisitor = YamlRenderer .this
113+ def subVisitor : Visitor [ StringWriter , StringWriter ] = YamlRenderer .this
100114 def visitValue (v : StringWriter , index : Int ): Unit = {
101115 empty = false
102116 flushBuffer()
103117 newlineBuffered = true
104118 dashBuffered = true
105119 }
106- def visitEnd (index : Int ) = {
120+ def visitEnd (index : Int ): StringWriter = {
107121 if (! dedentInObject) depth -= 1
108122 if (empty) {
109123 elemBuilder.ensureLength(2 )
@@ -116,16 +130,19 @@ class YamlRenderer(_out: StringWriter = new java.io.StringWriter(), indentArrayI
116130 _out
117131 }
118132 }
119- override def visitObject (length : Int , index : Int ) = new ObjVisitor [StringWriter , StringWriter ] {
133+
134+ override def visitObject (length : Int , index : Int ): ObjVisitor [StringWriter , StringWriter ] = new ObjVisitor [StringWriter , StringWriter ] {
120135 var empty = true
121136 flushBuffer()
122137 if (! topLevel) depth += 1
123138 topLevel = false
124139
125140 if (afterKey) newlineBuffered = true
126141
127- def subVisitor = YamlRenderer .this
128- def visitKey (index : Int ) = YamlRenderer .this
142+ def subVisitor : Visitor [StringWriter , StringWriter ] = YamlRenderer .this
143+
144+ def visitKey (index : Int ): Visitor [StringWriter , StringWriter ] = yamlKeyVisitor
145+
129146 def visitKeyValue (s : Any ): Unit = {
130147 empty = false
131148 flushBuffer()
@@ -136,11 +153,13 @@ class YamlRenderer(_out: StringWriter = new java.io.StringWriter(), indentArrayI
136153 afterKey = true
137154 newlineBuffered = false
138155 }
156+
139157 def visitValue (v : StringWriter , index : Int ): Unit = {
140158 newlineBuffered = true
141159 afterKey = false
142160 }
143- def visitEnd (index : Int ) = {
161+
162+ def visitEnd (index : Int ): StringWriter = {
144163 if (empty) {
145164 elemBuilder.ensureLength(2 )
146165 elemBuilder.append('{' )
@@ -155,9 +174,28 @@ class YamlRenderer(_out: StringWriter = new java.io.StringWriter(), indentArrayI
155174 }
156175}
157176object YamlRenderer {
158- val newlinePattern = Pattern .compile(" \n " )
177+ val newlinePattern : Pattern = Pattern .compile(" \n " )
178+ private val safeYamlKeyPattern = Pattern .compile(" ^[a-zA-Z0-9/._-]+$" )
179+ private val yamlReserved = Set (" true" , " false" , " null" , " yes" , " no" , " on" , " off" , " y" , " n" , " .nan" ,
180+ " +.inf" , " -.inf" , " .inf" , " null" , " -" , " ---" , " ''" )
181+ private val yamlTimestampPattern = Pattern .compile(" ^(?:[0-9]*-){2}[0-9]*$" )
182+ private val yamlBinaryPattern = Pattern .compile(" ^[-+]?0b[0-1_]+$" )
183+ private val yamlHexPattern = Pattern .compile(" [-+]?0x[0-9a-fA-F_]+" )
184+ private val yamlFloatPattern = Pattern .compile( " ^-?([0-9_]*)*(\\ .[0-9_]*)?(e[-+][0-9_]+)?$" )
185+ private val yamlIntPattern = Pattern .compile(" ^[-+]?[0-9_]+$" )
186+
187+ private def isSafeBareKey (k : String ) = {
188+ val l = k.toLowerCase
189+ ! yamlReserved.contains(l) &&
190+ safeYamlKeyPattern.matcher(k).matches() &&
191+ ! yamlTimestampPattern.matcher(l).matches() &&
192+ ! yamlBinaryPattern.matcher(k).matches() &&
193+ ! yamlHexPattern.matcher(k).matches() &&
194+ ! yamlFloatPattern.matcher(l).matches() &&
195+ ! yamlIntPattern.matcher(l).matches()
196+ }
159197
160- def writeIndentation (out : upickle.core.CharBuilder , n : Int ) = {
198+ def writeIndentation (out : upickle.core.CharBuilder , n : Int ): Unit = {
161199 out.ensureLength(n+ 1 )
162200 out.append('\n ' )
163201 var i = n
0 commit comments