Skip to content

Commit 8f697e5

Browse files
authored
Merge pull request #684 from lrytz/safeToString-backport
Backport stack safe toString to 1.x
2 parents a2d6d9a + c9c8f21 commit 8f697e5

File tree

9 files changed

+174
-161
lines changed

9 files changed

+174
-161
lines changed

Diff for: .github/workflows/ci.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: test
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
jobs:
8+
test:
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
java: [8, 11, 17]
13+
scala: [2.11.x, 2.12.x, 2.13.x]
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v2
17+
with:
18+
fetch-depth: 0
19+
- uses: actions/setup-java@v3
20+
with:
21+
distribution: temurin
22+
java-version: ${{matrix.java}}
23+
cache: sbt
24+
- name: Test
25+
run: sbt ++${{matrix.scala}} test versionPolicyCheck package

Diff for: .github/workflows/release.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Release
2+
on:
3+
push:
4+
tags: ["*"]
5+
jobs:
6+
publish:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v2
10+
with:
11+
fetch-depth: 0
12+
- uses: actions/setup-java@v2
13+
with:
14+
distribution: temurin
15+
java-version: 8
16+
- run: sbt versionCheck ci-release
17+
env:
18+
PGP_PASSPHRASE: ${{secrets.PGP_PASSPHRASE}}
19+
PGP_SECRET: ${{secrets.PGP_SECRET}}
20+
SONATYPE_PASSWORD: ${{secrets.SONATYPE_PASSWORD}}
21+
SONATYPE_USERNAME: ${{secrets.SONATYPE_USERNAME}}

Diff for: .travis.yml

-28
This file was deleted.

Diff for: build.sbt

+50-28
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,55 @@
11
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
22

3+
publish / skip := true // root project
4+
5+
ThisBuild / startYear := Some(2002)
6+
ThisBuild / licenses += (("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0")))
7+
8+
// because it doesn't declare it itself
9+
ThisBuild / libraryDependencySchemes += "org.scala-js" %% "scalajs-library" % "semver-spec"
10+
ThisBuild / apiURL := Some(url("https://javadoc.io/doc/org.scala-lang.modules/scala-xml_2.13/"))
11+
12+
lazy val configSettings: Seq[Setting[?]] = Seq(
13+
unmanagedSourceDirectories ++= {
14+
unmanagedSourceDirectories.value.flatMap { dir =>
15+
def forVersion(version: String): File = file(dir.getPath ++ "-" ++ version)
16+
CrossVersion.partialVersion(scalaVersion.value) match {
17+
case Some((2, 13)) => Seq(forVersion("2.13"))
18+
case _ => Seq(forVersion("2.11-2.12"))
19+
}
20+
}
21+
}
22+
)
23+
324
lazy val xml = crossProject(JSPlatform, JVMPlatform)
425
.withoutSuffixFor(JVMPlatform)
526
.crossType(CrossType.Full)
627
.in(file("."))
728
.settings(ScalaModulePlugin.scalaModuleSettings)
8-
.jvmSettings(ScalaModulePlugin.scalaModuleSettingsJVM)
29+
.jvmSettings(ScalaModulePlugin.scalaModuleOsgiSettings)
930
.settings(
1031
name := "scala-xml",
32+
scalaModuleAutomaticModuleName := Some("scala.xml"),
33+
crossScalaVersions := Seq("2.13.11", "2.12.18", "2.11.12"),
34+
scalaVersion := "2.12.18",
1135

12-
// Compiler team advised avoiding the -Xfuture option for releases.
13-
// The output with -Xfuture should be periodically checked, though.
14-
scalacOptions ++= "-deprecation:false -feature -Xlint:-stars-align,-nullary-unit,_".split("\\s+").to[Seq],
15-
scalacOptions in Test += "-Xxml:coalescing",
36+
scalacOptions ++= Seq("-deprecation:false", "-feature", "-Xlint:-stars-align,-nullary-unit,_"),
1637

17-
scalaModuleMimaPreviousVersion := {
18-
if (System.getenv("SCALAJS_VERSION") == "1.0.1") None
19-
else Some("1.2.0")
20-
},
38+
Test / scalacOptions += "-Xxml:coalescing",
2139

22-
unmanagedSourceDirectories in Compile ++= {
23-
(unmanagedSourceDirectories in Compile).value.map { dir =>
24-
val sv = scalaVersion.value
25-
CrossVersion.partialVersion(sv) match {
26-
case Some((2, 13)) => file(dir.getPath ++ "-2.13")
27-
case _ => file(dir.getPath ++ "-2.11-2.12")
28-
}
29-
}
30-
},
40+
headerLicense := Some(HeaderLicense.Custom(
41+
s"""|Scala (https://www.scala-lang.org)
42+
|
43+
|Copyright EPFL and Lightbend, Inc.
44+
|
45+
|Licensed under Apache License 2.0
46+
|(http://www.apache.org/licenses/LICENSE-2.0).
47+
|
48+
|See the NOTICE file distributed with this work for
49+
|additional information regarding copyright ownership.
50+
|""".stripMargin)),
3151

32-
apiURL := Some(
33-
url(s"""https://scala.github.io/scala-xml/api/${"-.*".r.replaceAllIn(version.value, "")}/""")
34-
),
52+
versionPolicyIntention := Compatibility.BinaryCompatible,
3553

3654
apiMappings ++= scalaInstance.value.libraryJars.filter { file =>
3755
file.getName.startsWith("scala-library") && file.getName.endsWith(".jar")
@@ -47,7 +65,7 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform)
4765
file(jarPath)
4866
-> url("http://docs.oracle.com/javase/8/docs/api")
4967
)
50-
} getOrElse {
68+
}.getOrElse {
5169
// If everything fails, jam in Java 11 modules.
5270
Map(
5371
file("/modules/java.base")
@@ -58,16 +76,20 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform)
5876
}
5977
}
6078
)
79+
.settings(
80+
inConfig(Compile)(configSettings) ++ inConfig(Test)(configSettings)
81+
)
6182
.jvmSettings(
6283
OsgiKeys.exportPackage := Seq(s"scala.xml.*;version=${version.value}"),
6384

64-
libraryDependencies += "junit" % "junit" % "4.13" % "test",
65-
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test",
66-
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.9" % "test",
67-
libraryDependencies += ("org.scala-lang" % "scala-compiler" % scalaVersion.value % "test").exclude("org.scala-lang.modules", s"scala-xml_${scalaBinaryVersion.value}")
85+
libraryDependencies += "junit" % "junit" % "4.13.2" % Test,
86+
libraryDependencies += "com.github.sbt" % "junit-interface" % "0.13.3" % Test,
87+
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.12.0" % Test,
88+
libraryDependencies += ("org.scala-lang" % "scala-compiler" % scalaVersion.value % Test).exclude("org.scala-lang.modules", s"scala-xml_${scalaBinaryVersion.value}")
6889
)
6990
.jsSettings(
91+
crossScalaVersions := crossScalaVersions.value.filterNot(_.startsWith("2.11")),
7092
// Scala.js cannot run forked tests
71-
fork in Test := false
93+
Test / fork := false
7294
)
73-
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))
95+
.jsEnablePlugins(ScalaJSJUnitPlugin)

Diff for: build.sh

-54
This file was deleted.

Diff for: project/build.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=1.3.13
1+
sbt.version=1.9.2

Diff for: project/plugins.sbt

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
val scalaJSVersion =
2-
Option(System.getenv("SCALAJS_VERSION")).filter(_.nonEmpty).getOrElse("0.6.33")
3-
4-
addSbtPlugin("org.scala-lang.modules" % "sbt-scala-module" % "2.1.3")
5-
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")
6-
addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion)
1+
addSbtPlugin("org.scala-lang.modules" % "sbt-scala-module" % "3.1.0")
2+
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
3+
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2")
4+
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.2")
5+
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14")
6+
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0")

Diff for: shared/src/main/scala/scala/xml/Utility.scala

+65-44
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package scala
1010
package xml
1111

12+
import scala.annotation.tailrec
1213
import scala.collection.mutable
1314
import scala.language.implicitConversions
1415
import scala.collection.Seq
@@ -187,9 +188,7 @@ object Utility extends AnyRef with parsing.TokenTests {
187188
decodeEntities: Boolean = true,
188189
preserveWhitespace: Boolean = false,
189190
minimizeTags: Boolean = false): StringBuilder =
190-
{
191191
serialize(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, if (minimizeTags) MinimizeMode.Always else MinimizeMode.Never)
192-
}
193192

194193
/**
195194
* Serialize an XML Node to a StringBuilder.
@@ -206,35 +205,66 @@ object Utility extends AnyRef with parsing.TokenTests {
206205
stripComments: Boolean = false,
207206
decodeEntities: Boolean = true,
208207
preserveWhitespace: Boolean = false,
209-
minimizeTags: MinimizeMode.Value = MinimizeMode.Default): StringBuilder =
210-
{
211-
x match {
212-
case c: Comment => if (!stripComments) c buildString sb; sb
213-
case s: SpecialNode => s buildString sb
214-
case g: Group =>
215-
for (c <- g.nodes) serialize(c, g.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags); sb
216-
case el: Elem =>
217-
// print tag with namespace declarations
218-
sb.append('<')
219-
el.nameToString(sb)
220-
if (el.attributes ne null) el.attributes.buildString(sb)
221-
el.scope.buildString(sb, pscope)
222-
if (el.child.isEmpty &&
223-
(minimizeTags == MinimizeMode.Always ||
224-
(minimizeTags == MinimizeMode.Default && el.minimizeEmpty))) {
225-
// no children, so use short form: <xyz .../>
226-
sb.append("/>")
227-
} else {
228-
// children, so use long form: <xyz ...>...</xyz>
229-
sb.append('>')
230-
sequenceToXML(el.child, el.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
231-
sb.append("</")
232-
el.nameToString(sb)
233-
sb.append('>')
234-
}
235-
case _ => throw new IllegalArgumentException("Don't know how to serialize a " + x.getClass.getName)
236-
}
208+
minimizeTags: MinimizeMode.Value = MinimizeMode.Default
209+
): StringBuilder = {
210+
serializeImpl(List(x), pscope, false, stripComments, minimizeTags, sb)
211+
sb
212+
}
213+
214+
private def serializeImpl(
215+
ns: Seq[Node],
216+
pscope: NamespaceBinding,
217+
spaced: Boolean,
218+
stripComments: Boolean,
219+
minimizeTags: MinimizeMode.Value,
220+
sb: StringBuilder
221+
): Unit = {
222+
@tailrec def ser(nss: List[List[Node]], pscopes: List[NamespaceBinding], spaced: List[Boolean], toClose: List[Node]): Unit = nss match {
223+
case List(Nil) =>
224+
case Nil :: rests =>
225+
if (toClose.head != null) {
226+
sb.append("</")
227+
toClose.head.nameToString(sb)
228+
sb.append('>')
229+
}
230+
ser(rests, pscopes.tail, spaced.tail, toClose.tail)
231+
case (n :: ns) :: r =>
232+
def sp(): Unit = if (ns.nonEmpty && spaced.head) sb.append(' ')
233+
n match {
234+
case c: Comment =>
235+
if (!stripComments) {
236+
c.buildString(sb)
237+
sp()
238+
}
239+
ser(ns :: r, pscopes, spaced, toClose)
240+
case s: SpecialNode =>
241+
s.buildString(sb)
242+
sp()
243+
ser(ns :: r, pscopes, spaced, toClose)
244+
case g: Group =>
245+
ser(g.nodes.toList :: ns :: r, g.scope :: pscopes, false :: spaced, null :: toClose)
246+
case e: Elem =>
247+
sb.append('<')
248+
e.nameToString(sb)
249+
if (e.attributes.ne(null)) e.attributes.buildString(sb)
250+
e.scope.buildString(sb, pscopes.head)
251+
if (e.child.isEmpty &&
252+
(minimizeTags == MinimizeMode.Always ||
253+
(minimizeTags == MinimizeMode.Default && e.minimizeEmpty))) {
254+
// no children, so use short form: <xyz .../>
255+
sb.append("/>")
256+
sp()
257+
ser(ns :: r, pscopes, spaced, toClose)
258+
} else {
259+
sb.append('>')
260+
val csp = e.child.forall(isAtomAndNotText)
261+
ser(e.child.toList :: ns :: r, e.scope :: pscopes, csp :: spaced, e :: toClose)
262+
}
263+
case n => throw new IllegalArgumentException("Don't know how to serialize a " + n.getClass.getName)
264+
}
237265
}
266+
ser(List(ns.toList), List(pscope), List(spaced), Nil)
267+
}
238268

239269
def sequenceToXML(
240270
children: Seq[Node],
@@ -243,20 +273,11 @@ object Utility extends AnyRef with parsing.TokenTests {
243273
stripComments: Boolean = false,
244274
decodeEntities: Boolean = true,
245275
preserveWhitespace: Boolean = false,
246-
minimizeTags: MinimizeMode.Value = MinimizeMode.Default): Unit =
247-
{
248-
if (children.isEmpty) return
249-
else if (children forall isAtomAndNotText) { // add space
250-
val it = children.iterator
251-
val f = it.next()
252-
serialize(f, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
253-
while (it.hasNext) {
254-
val x = it.next()
255-
sb.append(' ')
256-
serialize(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
257-
}
258-
} else children foreach { serialize(_, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) }
259-
}
276+
minimizeTags: MinimizeMode.Value = MinimizeMode.Default
277+
): Unit = if (children.nonEmpty) {
278+
val spaced = children.forall(isAtomAndNotText)
279+
serializeImpl(children, pscope, spaced, stripComments, minimizeTags, sb)
280+
}
260281

261282
/**
262283
* Returns prefix of qualified name if any.

0 commit comments

Comments
 (0)