Skip to content

Commit b7053b6

Browse files
authored
Merge pull request scala#558 from dubinsky/parse-cdata
Do not ignore XML CDATA sections when parsing:
2 parents 0f7d436 + 5e8aba5 commit b7053b6

File tree

6 files changed

+70
-18
lines changed

6 files changed

+70
-18
lines changed

build.sbt

+5-7
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,12 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform, NativePlatform)
6868
// to previous-version artifacts that were built on 8. see scala/scala-xml#501
6969
exclude[DirectMissingMethodProblem]("scala.xml.include.sax.XIncluder.declaration"),
7070

71-
// caused by the switch from DefaultHandler to DefaultHandler2:
72-
exclude[MissingTypesProblem]("scala.xml.parsing.FactoryAdapter"),
73-
exclude[MissingTypesProblem]("scala.xml.parsing.NoBindingFactoryAdapter"),
71+
// necessitated by the switch from DefaultHandler to DefaultHandler2 in FactoryAdapter:
72+
exclude[MissingTypesProblem]("scala.xml.parsing.FactoryAdapter"), // see #549
7473

75-
exclude[DirectMissingMethodProblem]("scala.xml.parsing.FactoryAdapter.comment"),
76-
exclude[ReversedMissingMethodProblem]("scala.xml.parsing.FactoryAdapter.createComment"),
77-
exclude[DirectMissingMethodProblem]("scala.xml.parsing.FactoryAdapter.createComment"),
78-
exclude[DirectMissingMethodProblem]("scala.xml.parsing.NoBindingFactoryAdapter.createComment")
74+
// necessitated by the introduction of new abstract methods in FactoryAdapter:
75+
exclude[ReversedMissingMethodProblem]("scala.xml.parsing.FactoryAdapter.createComment"), // see #549
76+
exclude[ReversedMissingMethodProblem]("scala.xml.parsing.FactoryAdapter.createPCData") // see #558
7977
)
8078
},
8179
// Mima signature checking stopped working after 3.0.2 upgrade, see #557

jvm/src/test/scala/scala/xml/XMLTest.scala

+26-5
Original file line numberDiff line numberDiff line change
@@ -580,13 +580,23 @@ class XMLTestJVM {
580580
XML.loadString(broken)
581581
}
582582

583+
def roundtrip(xml: String): Unit = assertEquals(xml, XML.loadString(xml).toString)
584+
583585
@UnitTest
584-
def issue508: Unit = {
585-
def check(xml: String): Unit = assertEquals(xml, XML.loadString(xml).toString)
586+
def issue508commentParsing: Unit = {
587+
// confirm that comments are processed correctly now
588+
roundtrip("<a><!-- comment --> suffix</a>")
589+
roundtrip("<a>prefix <!-- comment --> suffix</a>")
590+
roundtrip("<a>prefix <b><!-- comment --></b> suffix</a>")
591+
roundtrip("<a>prefix <b><!-- multi-\nline\n comment --></b> suffix</a>")
592+
roundtrip("""<a>prefix <b><!-- multi-
593+
|line
594+
| comment --></b> suffix</a>""".stripMargin)
586595

587-
check("<a><!-- comment --> suffix</a>")
588-
check("<a>prefix <!-- comment --> suffix</a>")
589-
check("<a>prefix <b><!-- comment --></b> suffix</a>")
596+
// confirm that processing instructions were always processed correctly
597+
roundtrip("<a><?target content ?> suffix</a>")
598+
roundtrip("<a>prefix <?target content ?> suffix</a>")
599+
roundtrip("<a>prefix <b><?target content?></b> suffix</a>")
590600

591601
// TODO since XMLLoader retrieves FactoryAdapter.rootNode,
592602
// capturing comments before and after the root element is not currently possible
@@ -595,6 +605,17 @@ class XMLTestJVM {
595605
//check("<a>text</a><!-- epilogue -->")
596606
}
597607

608+
@UnitTest
609+
def cdataParsing: Unit = {
610+
roundtrip("<a><![CDATA[ cdata ]]> suffix</a>")
611+
roundtrip("<a>prefix <![CDATA[ cdata ]]> suffix</a>")
612+
roundtrip("<a>prefix <b><![CDATA[ cdata section]]></b> suffix</a>")
613+
roundtrip("""<a>prefix <b><![CDATA[
614+
| multi-
615+
| line cdata
616+
| section]]></b> suffix</a>""".stripMargin)
617+
}
618+
598619
@UnitTest
599620
def nodeSeqNs: Unit = {
600621
val x = {

shared/src/main/scala/scala/xml/factory/NodeFactory.scala

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ trait NodeFactory[A <: Node] {
5656
}
5757

5858
def makeText(s: String): Text = Text(s)
59+
def makePCData(s: String): PCData =
60+
PCData(s)
5961
def makeComment(s: String): Seq[Comment] =
6062
if (ignoreComments) Nil else List(Comment(s))
6163
def makeProcInstr(t: String, s: String): Seq[ProcInstr] =

shared/src/main/scala/scala/xml/parsing/FactoryAdapter.scala

+34-5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ abstract class FactoryAdapter extends DefaultHandler2 with factory.XMLLoader[Nod
4343
var rootElem: Node = _
4444

4545
val buffer = new StringBuilder()
46+
private var inCDATA: Boolean = false
47+
4648
/** List of attributes
4749
*
4850
* Previously was a mutable [[scala.collection.mutable.Stack Stack]], but is now a mutable reference to an immutable [[scala.collection.immutable.List List]].
@@ -100,6 +102,13 @@ abstract class FactoryAdapter extends DefaultHandler2 with factory.XMLLoader[Nod
100102
*/
101103
def createText(text: String): Text // abstract
102104

105+
/**
106+
* creates a PCData node.
107+
* @param text
108+
* @return a new PCData node.
109+
*/
110+
def createPCData(text: String): PCData // abstract
111+
103112
/**
104113
* creates a new processing instruction node.
105114
*/
@@ -117,7 +126,7 @@ abstract class FactoryAdapter extends DefaultHandler2 with factory.XMLLoader[Nod
117126
val normalizeWhitespace = false
118127

119128
/**
120-
* Characters.
129+
* Capture characters, possibly normalizing whitespace.
121130
* @param ch
122131
* @param offset
123132
* @param length
@@ -139,7 +148,20 @@ abstract class FactoryAdapter extends DefaultHandler2 with factory.XMLLoader[Nod
139148
}
140149
}
141150

142-
private def splitName(s: String) = {
151+
/**
152+
* Start of a CDATA section.
153+
*/
154+
override def startCDATA(): Unit = {
155+
captureText()
156+
inCDATA = true
157+
}
158+
159+
/**
160+
* End of a CDATA section.
161+
*/
162+
override def endCDATA(): Unit = captureText()
163+
164+
private def splitName(s: String): (String, String) = {
143165
val idx = s indexOf ':'
144166
if (idx < 0) (null, s)
145167
else (s take idx, s drop (idx + 1))
@@ -185,13 +207,17 @@ abstract class FactoryAdapter extends DefaultHandler2 with factory.XMLLoader[Nod
185207
}
186208

187209
/**
188-
* captures text, possibly normalizing whitespace
210+
* Captures text or cdata.
189211
*/
190212
def captureText(): Unit = {
191-
if (capture && buffer.nonEmpty)
192-
hStack = createText(buffer.toString) :: hStack
213+
if (capture && buffer.nonEmpty) {
214+
val text: String = buffer.toString
215+
val newNode: Node = if (inCDATA) createPCData(text) else createText(text)
216+
hStack = newNode :: hStack
217+
}
193218

194219
buffer.clear()
220+
inCDATA = false
195221
}
196222

197223
/**
@@ -232,6 +258,9 @@ abstract class FactoryAdapter extends DefaultHandler2 with factory.XMLLoader[Nod
232258
hStack = hStack.reverse_:::(createProcInstr(target, data).toList)
233259
}
234260

261+
/**
262+
* Comment.
263+
*/
235264
override def comment(ch: Array[Char], start: Int, length: Int): Unit = {
236265
captureText()
237266
val commentText: String = String.valueOf(ch.slice(start, start + length))

shared/src/main/scala/scala/xml/parsing/NoBindingFactoryAdapter.scala

+3
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,7 @@ class NoBindingFactoryAdapter extends FactoryAdapter with NodeFactory[Elem] {
4242

4343
/** Creates a comment. */
4444
override def createComment(characters: String): Seq[Comment] = makeComment(characters)
45+
46+
/** Creates a cdata. */
47+
override def createPCData(characters: String): PCData = makePCData(characters)
4548
}

shared/src/test/scala/scala/xml/CommentTest.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package scala.xml
22

33
import org.junit.Assert.assertEquals
4-
import org.junit.Assert.assertTrue
54
import org.junit.Test
65

76
final class CommentTest {

0 commit comments

Comments
 (0)