Skip to content

Commit fc69196

Browse files
committed
Bugfix: Properly escape and unescape special XML chars
1 parent c93501f commit fc69196

3 files changed

Lines changed: 31 additions & 10 deletions

File tree

compiler/shared/src/main/scala/xml/SimpleXMLParser.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ class SimpleXMLParser(text: String) {
4141
elements
4242
}
4343

44+
private val unescapes = Seq[(String, String)]("&amp;" -> "&", "&lt;" -> "<", "&gt;" -> ">", "&quot;" -> "\"")
45+
46+
def unescapeValue(text: String): String = {
47+
unescapes.foldLeft(text)((current: String, checks: (String, String)) => { current.replace(checks._1, checks._2) })
48+
}
49+
4450
def parseAttribute(): (String, String) = {
4551
val nameStart = reader.getCurrentIndex
4652
reader.skipUntil('=')
@@ -51,7 +57,9 @@ class SimpleXMLParser(text: String) {
5157
reader.skipUntil('"')
5258
val valEnd = reader.getCurrentIndex
5359
reader.next()
54-
(reader.substring(nameStart, nameEnd), reader.substring(valStart, valEnd))
60+
val attrName = reader.substring(nameStart, nameEnd)
61+
val attrValue = unescapeValue(reader.substring(valStart, valEnd))
62+
(attrName, attrValue)
5563
}
5664

5765
def parseAttributes(): (String, Map[String, String]) = {

compiler/shared/src/main/scala/xml/SimpleXMLWriter.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ class SimpleXMLWriter extends NLogoXMLWriter {
6060
}
6161

6262
def attribute(name: String, value: String): Unit = {
63-
builder.append(s" $name=\"$value\"")
63+
builder.append(s" $name=\"${escapeValue(value)}\"")
64+
}
65+
66+
private val escapes = Map[Char, String]('&' -> "&amp;", '<' -> "&lt;", '>' -> "&gt;", '"' -> "&quot;")
67+
68+
def escapeValue(text: String): String = {
69+
text.flatMap( (c) => escapes.getOrElse(c, c).toString )
6470
}
6571

6672
def escapedText(text: String): Unit = {

compiler/shared/src/test/scala/xml/TortoiseModelLoaderTest.scala

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
package org.nlogo.tortoise.compiler.xml
44

5-
import org.nlogo.core.{ Model, View, WorldDimensions }
5+
import org.nlogo.core.{ Button, Model, View, WorldDimensions }
66

77
import org.nlogo.core.XMLElement
88

@@ -56,11 +56,11 @@ class TortoiseModelLoaderTest extends AnyFunSuite {
5656
<widgets>
5757
<view x="455" wrappingAllowedX="true" y="10" frameRate="30.0" minPycor="-25" height="564" showTickCounter="true" patchSize="10.9804" fontSize="14" wrappingAllowedY="true" width="564" tickCounterLabel="ticks" maxPycor="25" updateMode="1" maxPxcor="25" minPxcor="-25"></view>
5858
<slider x="5" step="1.0" y="283" max="20.0" width="220" display="sheep-reproduce" height="50" min="1.0" direction="Horizontal" default="4.0" variable="sheep-reproduce" units="%"></slider>
59-
<monitor x="350" precision="0" y="498" height="60" fontSize="11" width="100" display="grass">count grass / 4</monitor>
60-
<plot x="5" autoPlotX="true" yMax="100.0" autoPlotY="true" yAxis="pop." y="342" xMin="0.0" height="235" legend="true" xMax="100.0" yMin="0.0" width="340" xAxis="time" display="populations">
59+
<monitor x="350" precision="0" y="498" height="60" fontSize="11" width="100" display="grass &amp; &lt;">count grass / 4</monitor>
60+
<plot x="5" autoPlotX="true" yMax="100.0" autoPlotY="true" yAxis="pop." y="342" xMin="0.0" height="235" legend="true" xMax="100.0" yMin="0.0" width="340" xAxis="time" display="populations &amp; &lt;">
6161
<setup></setup>
6262
<update></update>
63-
<pen interval="1.0" mode="0" display="sheep" color="-612749" legend="true">
63+
<pen interval="1.0" mode="0" display="sheep &amp; &lt;" color="-612749" legend="true">
6464
<setup></setup>
6565
<update>plot count sheep</update>
6666
</pen>
@@ -88,11 +88,11 @@ class TortoiseModelLoaderTest extends AnyFunSuite {
8888
val expectedWidgets = Seq(
8989
XMLElement("view", Map("x" -> "455", "wrappingAllowedX" -> "true", "y" -> "10", "frameRate" -> "30.0", "minPycor" -> "-25", "height" -> "564", "showTickCounter" -> "true", "patchSize" -> "10.9804", "fontSize" -> "14", "wrappingAllowedY" -> "true", "width" -> "564", "tickCounterLabel" -> "ticks", "maxPycor" -> "25", "updateMode" -> "1", "maxPxcor" -> "25", "minPxcor" -> "-25"), "", Seq()),
9090
XMLElement("slider", Map("x" -> "5", "step" -> "1.0", "y" -> "283", "max" -> "20.0", "width" -> "220", "display" -> "sheep-reproduce", "height" -> "50", "min" -> "1.0", "direction" -> "Horizontal", "default" -> "4.0", "variable" -> "sheep-reproduce", "units" -> "%"), "", Seq()),
91-
XMLElement("monitor", Map("x" -> "350", "precision" -> "0", "y" -> "498", "height" -> "60", "fontSize" -> "11", "width" -> "100", "display" -> "grass"), "count grass / 4", Seq()),
92-
XMLElement("plot", Map("x" -> "5", "autoPlotX" -> "true", "yMax" -> "100.0", "autoPlotY" -> "true", "yAxis" -> "pop.", "y" -> "342", "xMin" -> "0.0", "height" -> "235", "legend" -> "true", "xMax" -> "100.0", "yMin" -> "0.0", "width" -> "340", "xAxis" -> "time", "display" -> "populations"), "", Seq(
91+
XMLElement("monitor", Map("x" -> "350", "precision" -> "0", "y" -> "498", "height" -> "60", "fontSize" -> "11", "width" -> "100", "display" -> "grass & <"), "count grass / 4", Seq()),
92+
XMLElement("plot", Map("x" -> "5", "autoPlotX" -> "true", "yMax" -> "100.0", "autoPlotY" -> "true", "yAxis" -> "pop.", "y" -> "342", "xMin" -> "0.0", "height" -> "235", "legend" -> "true", "xMax" -> "100.0", "yMin" -> "0.0", "width" -> "340", "xAxis" -> "time", "display" -> "populations & <"), "", Seq(
9393
XMLElement("setup", Map(), "", Seq()),
9494
XMLElement("update", Map(), "", Seq()),
95-
XMLElement("pen", Map("interval" -> "1.0", "mode" -> "0", "display" -> "sheep", "color" -> "-612749", "legend" -> "true"), "", Seq(XMLElement("setup", Map(), "", Seq()), XMLElement("update", Map(), "plot count sheep", Seq()))),
95+
XMLElement("pen", Map("interval" -> "1.0", "mode" -> "0", "display" -> "sheep & <", "color" -> "-612749", "legend" -> "true"), "", Seq(XMLElement("setup", Map(), "", Seq()), XMLElement("update", Map(), "plot count sheep", Seq()))),
9696
XMLElement("pen", Map("interval" -> "1.0", "mode" -> "0", "display" -> "wolves", "color" -> "-16449023", "legend" -> "true"), "", Seq(XMLElement("setup", Map(), "", Seq()), XMLElement("update", Map(), "plot count wolves", Seq()))),
9797
XMLElement("pen", Map("interval" -> "1.0", "mode" -> "0", "display" -> "grass / 4", "color" -> "-10899396", "legend" -> "true"), "", Seq(XMLElement("setup", Map(), "", Seq()), XMLElement("update", Map(), "if model-version = \"sheep-wolves-grass\" [ plot count grass / 4 ]", Seq())))
9898
)),
@@ -200,6 +200,7 @@ class TortoiseModelLoaderTest extends AnyFunSuite {
200200
<code><![CDATA[to setup end <xml-tags-don't-matter-here></xml>]]></code>
201201
<widgets>
202202
<view x="455" wrappingAllowedX="true" y="10" frameRate="30.0" minPycor="-25" height="564" showTickCounter="true" patchSize="10.9804" fontSize="14" wrappingAllowedY="true" width="564" tickCounterLabel="ticks" maxPycor="25" updateMode="1" maxPxcor="25" minPxcor="-25"></view>
203+
<button x="180" y="145" height="40" disableUntilTicks="true" forever="true" kind="Observer" width="95" display="go &amp; &lt; &gt; &quot;">go</button>
203204
</widgets>
204205
<!-- comment is ignored <xml /> -->
205206
<info><![CDATA[# Hello! <h1> <h2> </p>]]></info>
@@ -211,7 +212,12 @@ class TortoiseModelLoaderTest extends AnyFunSuite {
211212

212213
test("TortoiseModelLoader reads very simple model") {
213214
val model = TortoiseModelLoader.read(simpleModelSource).get
214-
val expected = new Model("to setup end <xml-tags-don't-matter-here></xml>", Seq(View(455, 10, 564, 564, WorldDimensions(-25, 25, -25, 25, 10.9804, true, true), 14)), "# Hello! <h1> <h2> </p>", "NetLogo 7.0.0-beta2", Seq(), Seq())
215+
val expected = new Model("to setup end <xml-tags-don't-matter-here></xml>",
216+
Seq(
217+
View(455, 10, 564, 564, WorldDimensions(-25, 25, -25, 25, 10.9804, true, true), 14),
218+
Button(Some("go"), 180, 145, 95, 40, display = Some("go & < > \""), forever = true, disableUntilTicksStart = true)
219+
),
220+
"# Hello! <h1> <h2> </p>", "NetLogo 7.0.0-beta2", Seq(), Seq())
215221
// The `Section` class doesn't have value-equality, so we just ignore them for now
216222
assertResult(expected)(model.copy(optionalSections = Seq()))
217223
}
@@ -225,6 +231,7 @@ class TortoiseModelLoaderTest extends AnyFunSuite {
225231
<code><![CDATA[to setup end <xml-tags-don't-matter-here></xml>]]></code>
226232
<widgets>
227233
<view x="455" wrappingAllowedX="true" y="10" frameRate="30.0" minPycor="-25" height="564" showTickCounter="true" patchSize="10.9804" fontSize="14" wrappingAllowedY="true" width="564" tickCounterLabel="ticks" maxPycor="25" updateMode="1" maxPxcor="25" minPxcor="-25"></view>
234+
<button x="180" y="145" height="40" disableUntilTicks="true" forever="true" kind="Observer" width="95" display="go &amp; &lt; &gt; &quot;">go</button>
228235
</widgets>
229236
<info><![CDATA[# Hello! <h1> <h2> </p>]]></info>
230237
<turtleShapes></turtleShapes>

0 commit comments

Comments
 (0)