Skip to content

Commit 7662284

Browse files
acruiseadriaanm
authored andcommitted
SI-1118 WIP
1 parent 63e1389 commit 7662284

20 files changed

+139
-62
lines changed

Diff for: src/library/scala/xml/Elem.scala

100644100755
+27-8
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,18 @@ package scala.xml
1717
* @author Burak Emir <bqe@google.com>
1818
*/
1919
object Elem {
20-
def apply(prefix: String,label: String, attributes: MetaData, scope: NamespaceBinding, child: Node*) =
21-
new Elem(prefix, label, attributes, scope, child:_*)
20+
/** Build an Elem, setting its minimizeEmpty property to <code>true</code> if it has no children. Note that this
21+
* default may not be exactly what you want, as some XML dialects don't permit some elements to be minimized.
22+
*
23+
* @deprecated This factory method is retained for backward compatibility; please use the other one, with which you
24+
* can specify your own preference for minimizeEmpty.
25+
*/
26+
@deprecated
27+
def apply(prefix: String,label: String, attributes: MetaData, scope: NamespaceBinding, child: Node*): Elem =
28+
apply(prefix, label, attributes, scope, child.isEmpty, child: _*)
29+
30+
def apply(prefix: String,label: String, attributes: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, child: Node*): Elem =
31+
new Elem(prefix,label,attributes,scope, minimizeEmpty, child:_*)
2232

2333
def unapplySeq(n: Node) = n match {
2434
case _: SpecialNode | _: Group => None
@@ -29,11 +39,13 @@ object Elem {
2939
/** The case class `Elem` extends the `Node` class,
3040
* providing an immutable data object representing an XML element.
3141
*
32-
* @param prefix namespace prefix (may be null, but not the empty string)
33-
* @param label the element name
34-
* @param attribute the attribute map
35-
* @param scope the scope containing the namespace bindings
36-
* @param child the children of this node
42+
* @param prefix namespace prefix (may be null, but not the empty string)
43+
* @param label the element name
44+
* @param attributes1 the attribute map
45+
* @param scope the scope containing the namespace bindings
46+
* @param minimizeEmpty `true` if this element should be serialized as minimized (i.e. "&lt;el/&gt;") when
47+
* empty; `false` if it should be written out in long form.
48+
* @param child the children of this node
3749
*
3850
* Copyright 2008 Google Inc. All Rights Reserved.
3951
* @author Burak Emir <bqe@google.com>
@@ -43,9 +55,15 @@ class Elem(
4355
val label: String,
4456
attributes1: MetaData,
4557
override val scope: NamespaceBinding,
58+
val minimizeEmpty: Boolean,
4659
val child: Node*)
4760
extends Node with Serializable
4861
{
62+
@deprecated(since="2.10", message="this constructor is retained for backward compatibility. Please use the primary constructor, which lets you specify your own preference for `minimizeEmpty`.")
63+
def this(prefix: String, label: String, attributes: MetaData, scope: NamespaceBinding, child: Node*) = {
64+
this(prefix, label, attributes, scope, child.isEmpty, child: _*)
65+
}
66+
4967
final override def doCollectNamespaces = true
5068
final override def doTransform = true
5169

@@ -83,8 +101,9 @@ extends Node with Serializable
83101
label: String = this.label,
84102
attributes: MetaData = this.attributes,
85103
scope: NamespaceBinding = this.scope,
104+
minimizeEmpty: Boolean = this.minimizeEmpty,
86105
child: Seq[Node] = this.child.toSeq
87-
): Elem = Elem(prefix, label, attributes, scope, child: _*)
106+
): Elem = Elem(prefix, label, attributes, scope, minimizeEmpty, child: _*)
88107

89108
/** Returns concatenation of `text(n)` for each child `n`.
90109
*/

Diff for: src/library/scala/xml/Node.scala

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ abstract class Node extends NodeSeq {
159159
* @return ...
160160
*/
161161
def buildString(stripComments: Boolean): String =
162-
Utility.toXML(this, stripComments = stripComments).toString
162+
Utility.serialize(this, stripComments = stripComments).toString
163163

164164
/**
165165
* Same as `toString('''false''')`.

Diff for: src/library/scala/xml/PrettyPrinter.scala

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class PrettyPrinter(width: Int, step: Int) {
161161
case _ =>
162162
val test = {
163163
val sb = new StringBuilder()
164-
Utility.toXML(node, pscope, sb, false)
164+
Utility.serialize(node, pscope, sb, false)
165165
if (doPreserve(node)) sb.toString
166166
else TextBuffer.fromString(sb.toString).toText(0).data
167167
}

Diff for: src/library/scala/xml/Utility.scala

100644100755
+49-17
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ object Utility extends AnyRef with parsing.TokenTests {
181181
// sb.toString()
182182
// }
183183

184+
/**
185+
* Serialize the provided Node to the provided StringBuilder.
186+
* <p/>
187+
* Note that calling this source-compatible method will result in the same old, arguably almost universally unwanted,
188+
* behaviour.
189+
* @deprecated please use {@link #serialize} instead and specify a minimizeTags parameter.
190+
*/
191+
@deprecated
184192
def toXML(
185193
x: Node,
186194
pscope: NamespaceBinding = TopScope,
@@ -189,30 +197,54 @@ object Utility extends AnyRef with parsing.TokenTests {
189197
decodeEntities: Boolean = true,
190198
preserveWhitespace: Boolean = false,
191199
minimizeTags: Boolean = false): StringBuilder =
200+
{
201+
serialize(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, if (minimizeTags) MinimizeMode.Always else Mini
202+
}
203+
204+
/**
205+
* Serialize an XML Node to a StringBuilder.
206+
*
207+
* This is essentially a minor rework of {@link #toXML} that can't have the same name due to an unfortunate
208+
* combination of named/default arguments and overloading.
209+
*
210+
* @todo seriously consider just changing the default to {@link MinimizeMode#Default} so that the serialization is
211+
* transparent by default
212+
* @todo use a Writer instead
213+
*/
214+
def serialize(
215+
x: Node,
216+
pscope: NamespaceBinding = TopScope,
217+
sb: StringBuilder = new StringBuilder,
218+
stripComments: Boolean = false,
219+
decodeEntities: Boolean = true,
220+
preserveWhitespace: Boolean = false,
221+
minimizeTags: MinimizeMode.Value = MinimizeMode.Default): StringBuilder =
192222
{
193223
x match {
194-
case c: Comment => if (!stripComments) c buildString sb else sb
195-
case x: SpecialNode => x buildString sb
196-
case g: Group =>
197-
g.nodes foreach {toXML(_, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)}
198-
sb
199-
case _ =>
224+
case c: Comment if !stripComments => c buildString sb
225+
case s: SpecialNode => s buildString sb
226+
case g: Group => for (c <- g.nodes) serialize(c, g.scope, sb, minimizeTags = minimizeTags) ; sb
227+
case el: Elem =>
200228
// print tag with namespace declarations
201229
sb.append('<')
202-
x.nameToString(sb)
203-
if (x.attributes ne null) x.attributes.buildString(sb)
204-
x.scope.buildString(sb, pscope)
205-
if (x.child.isEmpty && minimizeTags) {
230+
el.nameToString(sb)
231+
if (el.attributes ne null) el.attributes.buildString(sb)
232+
el.scope.buildString(sb, pscope)
233+
if (el.child.isEmpty &&
234+
(minimizeTags == MinimizeMode.Always ||
235+
(minimizeTags == MinimizeMode.Default && el.minimizeEmpty)))
236+
{
206237
// no children, so use short form: <xyz .../>
207-
sb.append(" />")
238+
sb.append("/>")
208239
} else {
209240
// children, so use long form: <xyz ...>...</xyz>
210241
sb.append('>')
211-
sequenceToXML(x.child, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
242+
sequenceToXML(el.child, el.scope, sb, stripComments)
212243
sb.append("</")
213-
x.nameToString(sb)
244+
el.nameToString(sb)
214245
sb.append('>')
215246
}
247+
case _ => throw new IllegalArgumentException("Don't know how to serialize a " + x.getClass.getName)
216248
}
217249
}
218250

@@ -223,20 +255,20 @@ object Utility extends AnyRef with parsing.TokenTests {
223255
stripComments: Boolean = false,
224256
decodeEntities: Boolean = true,
225257
preserveWhitespace: Boolean = false,
226-
minimizeTags: Boolean = false): Unit =
258+
minimizeTags: MinimizeMode.Value = MinimizeMode.Default): Unit =
227259
{
228260
if (children.isEmpty) return
229261
else if (children forall isAtomAndNotText) { // add space
230262
val it = children.iterator
231263
val f = it.next
232-
toXML(f, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
264+
serialize(f, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
233265
while (it.hasNext) {
234266
val x = it.next
235267
sb.append(' ')
236-
toXML(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
268+
serialize(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
237269
}
238270
}
239-
else children foreach { toXML(_, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) }
271+
else children foreach { serialize(_, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) }
240272
}
241273

242274
/**

Diff for: src/library/scala/xml/XML.scala

100644100755
+17-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ object Source
2626
def fromSysId(sysID: String) = new InputSource(sysID)
2727
def fromString(string: String) = fromReader(new StringReader(string))
2828
}
29+
30+
/**
31+
* Governs how empty elements (i.e. those without children) should be serialized.
32+
*/
33+
object MinimizeMode extends Enumeration {
34+
/** Minimize empty tags if they were originally empty when parsed, or if they were constructed with {@link Elem#minimizeEmpty} == true */
35+
val Default = Value
36+
37+
/** Always minimize empty tags. Note that this may be problematic for XHTML, in which case {@link Xhtml#toXhtml} should be used instead. */
38+
val Always = Value
39+
40+
/** Never minimize empty tags. */
41+
val Never = Value
42+
}
43+
2944
import Source._
3045

3146
/** The object `XML` provides constants, and functions to load
@@ -83,10 +98,10 @@ object XML extends XMLLoader[Elem]
8398
* @param xmlDecl if true, write xml declaration
8499
* @param doctype if not null, write doctype declaration
85100
*/
86-
final def write(w: java.io.Writer, node: Node, enc: String, xmlDecl: Boolean, doctype: dtd.DocType) {
101+
final def write(w: java.io.Writer, node: Node, enc: String, xmlDecl: Boolean, doctype: dtd.DocType, minimizeTags: MinimizeMode.Value = MinimizeMode.Default) {
87102
/* TODO: optimize by giving writer parameter to toXML*/
88103
if (xmlDecl) w.write("<?xml version='1.0' encoding='" + enc + "'?>\n")
89104
if (doctype ne null) w.write( doctype.toString() + "\n")
90-
w.write(Utility.toXML(node).toString)
105+
w.write(Utility.serialize(node, minimizeTags = minimizeTags).toString)
91106
}
92107
}

Diff for: src/library/scala/xml/factory/Binder.scala

100644100755
+5-5
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ abstract class Binder(val preserveWS: Boolean) extends ValidatingMarkupHandler {
4343
result &+ text(0, x.data)
4444
case x:EntityRef =>
4545
result &+ entityRef(0, x.entityName)
46-
case _ =>
47-
elemStart(0, n.prefix, n.label, n.attributes, n.scope)
46+
case x:Elem =>
47+
elemStart(0, x.prefix, x.label, x.attributes, x.scope)
4848
val old = result
4949
result = new NodeBuffer()
50-
for (m <- n.child) traverse(m)
51-
result = old &+ elem(0, n.prefix, n.label, n.attributes, n.scope, NodeSeq.fromSeq(result)).toList;
52-
elemEnd(0, n.prefix, n.label)
50+
for (m <- x.child) traverse(m)
51+
result = old &+ elem(0, x.prefix, x.label, x.attributes, x.scope, x.minimizeEmpty, NodeSeq.fromSeq(result)).toList;
52+
elemEnd(0, x.prefix, x.label)
5353
}
5454

5555
final def validate(n: Node): Node = {

Diff for: src/library/scala/xml/parsing/ConstructingHandler.scala

100644100755
+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ abstract class ConstructingHandler extends MarkupHandler
2121
val preserveWS: Boolean
2222

2323
def elem(pos: Int, pre: String, label: String, attrs: MetaData,
24-
pscope: NamespaceBinding, nodes: NodeSeq): NodeSeq =
25-
Elem(pre, label, attrs, pscope, nodes:_*)
24+
pscope: NamespaceBinding, empty: Boolean, nodes: NodeSeq): NodeSeq =
25+
Elem(pre, label, attrs, pscope, empty, nodes:_*)
2626

2727
def procInstr(pos: Int, target: String, txt: String) =
2828
ProcInstr(target, txt)

Diff for: src/library/scala/xml/parsing/DefaultMarkupHandler.scala

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ package parsing
1616
abstract class DefaultMarkupHandler extends MarkupHandler {
1717

1818
def elem(pos: Int, pre: String, label: String, attrs: MetaData,
19-
scope:NamespaceBinding, args: NodeSeq) = NodeSeq.Empty
19+
scope:NamespaceBinding, empty: Boolean, args: NodeSeq) = NodeSeq.Empty
2020

2121
def procInstr(pos: Int, target: String, txt: String) = NodeSeq.Empty
2222

Diff for: src/library/scala/xml/parsing/MarkupHandler.scala

100644100755
+2-1
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,11 @@ abstract class MarkupHandler extends Logged
7575
* @param pre the prefix
7676
* @param label the local name
7777
* @param attrs the attributes (metadata)
78+
* @param empty `true` if the element was previously empty; `false` otherwise.
7879
* @param args the children of this element
7980
* @return ...
8081
*/
81-
def elem(pos: Int, pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, args: NodeSeq): NodeSeq
82+
def elem(pos: Int, pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, empty: Boolean, args: NodeSeq): NodeSeq
8283

8384
/** callback method invoked by MarkupParser after parsing PI.
8485
*/

Diff for: src/library/scala/xml/parsing/MarkupParser.scala

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests
569569
tmp
570570
}
571571
}
572-
val res = handle.elem(pos, pre, local, aMap, scope, ts)
572+
val res = handle.elem(pos, pre, local, aMap, scope, ts == NodeSeq.Empty, ts)
573573
handle.elemEnd(pos, pre, local)
574574
res
575575
}

Diff for: src/library/scala/xml/pull/XMLEventReader.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ extends collection.AbstractIterator[XMLEvent]
8181
// memory usage optimization return one <ignore/> for top level to satisfy
8282
// MarkupParser.document() otherwise NodeSeq.Empty
8383
private var ignoreWritten = false
84-
final def elem(pos: Int, pre: String, label: String, attrs: MetaData, pscope: NamespaceBinding, nodes: NodeSeq): NodeSeq =
84+
final def elem(pos: Int, pre: String, label: String, attrs: MetaData, pscope: NamespaceBinding, empty: Boolean, nodes: NodeSeq): NodeSeq =
8585
if (level == 1 && !ignoreWritten) {ignoreWritten = true; <ignore/> } else NodeSeq.Empty
8686

8787
def procInstr(pos: Int, target: String, txt: String) = setEvent(EvProcInstr(target, txt))

Diff for: test/files/jvm/interpreter.check

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ scala> <a>
301301
/></a>
302302
res8: scala.xml.Elem =
303303
<a>
304-
<b c="c" d="dd"></b></a>
304+
<b c="c" d="dd"/></a>
305305

306306
scala>
307307

Diff for: test/files/jvm/t0632.check

100644100755
+12-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
<foo x="&amp;"></foo>
2-
<foo x="&amp;"></foo>
3-
<foo x="&amp;"></foo>
4-
<foo x="&amp;"></foo>
5-
<foo x="&amp;amp;"></foo>
6-
<foo x="&amp;amp;"></foo>
7-
<foo x="&amp;amp;"></foo>
8-
<foo x="&amp;amp;"></foo>
9-
<foo x="&amp;&amp;"></foo>
10-
<foo x="&amp;&amp;"></foo>
11-
<foo x="&amp;&amp;"></foo>
12-
<foo x="&amp;&amp;"></foo>
1+
<foo x="&amp;"/>
2+
<foo x="&amp;"/>
3+
<foo x="&amp;"/>
4+
<foo x="&amp;"/>
5+
<foo x="&amp;amp;"/>
6+
<foo x="&amp;amp;"/>
7+
<foo x="&amp;amp;"/>
8+
<foo x="&amp;amp;"/>
9+
<foo x="&amp;&amp;"/>
10+
<foo x="&amp;&amp;"/>
11+
<foo x="&amp;&amp;"/>
12+
<foo x="&amp;&amp;"/>

Diff for: test/files/jvm/t1118.check

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<hi/> <!-- literal short -->
2+
<there></there> <!-- literal long -->
3+
<guys who="you all"></guys> <!-- literal long with attribute-->
4+
<hows it="going"/> <!-- literal short with attribute -->
5+
<this>scala stuff is pretty cool</this> <!-- literal not empty -->
6+
7+
<bob></bob> <!--programmatic long-->
8+
<dobbs/> <!--programmatic short-->
9+
<is really="yep"/> <!--programmatic short with attribute-->
10+
<slack sing="it"></slack> <!--programmatic long with attribute-->

Diff for: test/files/jvm/xml01.check

100644100755
+2-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ xpath \
33
xpath \\ DESCENDANTS
44
<book><author>Peter Buneman</author><author>Dan Suciu</author><title>Data on ze web</title></book>
55
-- group nodes
6-
<f><a></a><b></b><c></c></f>
7-
<a></a><f><a></a><b></b><c></c></f><a></a><b></b><c></c>
6+
<f><a/><b/><c/></f>
7+
<a/><f><a/><b/><c/></f><a/><b/><c/>
88
attribute value normalization

Diff for: test/files/jvm/xml03syntax.check

100644100755
+2-2
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ true
2222
2
2323
4
2424

25-
node=<elem key="<b>hello</b>"></elem>, key=Some(<b>hello</b>)
26-
node=<elem></elem>, key=None
25+
node=<elem key="<b>hello</b>"/>, key=Some(<b>hello</b>)
26+
node=<elem/>, key=None

Diff for: test/files/run/t0663.check

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<feed></feed>
1+
<feed/>

Diff for: test/files/run/t1620.check

100644100755
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version='1.0' encoding='utf-8'?>
22
<!DOCTYPE foo PUBLIC "-//Foo Corp//DTD 1.0//EN" "foo.dtd">
3-
<foo></foo>
3+
<foo/>
44
<?xml version='1.0' encoding='utf-8'?>
55
<!DOCTYPE foo PUBLIC "-//Foo Corp//DTD 1.0//EN">
6-
<foo></foo>
6+
<foo/>

Diff for: test/files/run/t2124.check

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p><lost></lost><q></q></p>
1+
<p><lost/><q/></p>

Diff for: test/files/run/t2125.check

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<p><lost></lost><q></q></p>
1+
<p><lost/><q></q></p>

0 commit comments

Comments
 (0)