Skip to content

Commit

Permalink
Merge pull request #684 from lrytz/safeToString-backport
Browse files Browse the repository at this point in the history
Backport stack safe toString to 1.x
  • Loading branch information
lrytz authored Jul 13, 2023
2 parents a2d6d9a + c9c8f21 commit 8f697e5
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 161 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: test
on:
push:
branches:
- main
pull_request:
jobs:
test:
strategy:
fail-fast: false
matrix:
java: [8, 11, 17]
scala: [2.11.x, 2.12.x, 2.13.x]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: ${{matrix.java}}
cache: sbt
- name: Test
run: sbt ++${{matrix.scala}} test versionPolicyCheck package
21 changes: 21 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Release
on:
push:
tags: ["*"]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 8
- run: sbt versionCheck ci-release
env:
PGP_PASSPHRASE: ${{secrets.PGP_PASSPHRASE}}
PGP_SECRET: ${{secrets.PGP_SECRET}}
SONATYPE_PASSWORD: ${{secrets.SONATYPE_PASSWORD}}
SONATYPE_USERNAME: ${{secrets.SONATYPE_USERNAME}}
28 changes: 0 additions & 28 deletions .travis.yml

This file was deleted.

78 changes: 50 additions & 28 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,37 +1,55 @@
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}

publish / skip := true // root project

ThisBuild / startYear := Some(2002)
ThisBuild / licenses += (("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0")))

// because it doesn't declare it itself
ThisBuild / libraryDependencySchemes += "org.scala-js" %% "scalajs-library" % "semver-spec"
ThisBuild / apiURL := Some(url("https://javadoc.io/doc/org.scala-lang.modules/scala-xml_2.13/"))

lazy val configSettings: Seq[Setting[?]] = Seq(
unmanagedSourceDirectories ++= {
unmanagedSourceDirectories.value.flatMap { dir =>
def forVersion(version: String): File = file(dir.getPath ++ "-" ++ version)
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, 13)) => Seq(forVersion("2.13"))
case _ => Seq(forVersion("2.11-2.12"))
}
}
}
)

lazy val xml = crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("."))
.settings(ScalaModulePlugin.scalaModuleSettings)
.jvmSettings(ScalaModulePlugin.scalaModuleSettingsJVM)
.jvmSettings(ScalaModulePlugin.scalaModuleOsgiSettings)
.settings(
name := "scala-xml",
scalaModuleAutomaticModuleName := Some("scala.xml"),
crossScalaVersions := Seq("2.13.11", "2.12.18", "2.11.12"),
scalaVersion := "2.12.18",

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

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

unmanagedSourceDirectories in Compile ++= {
(unmanagedSourceDirectories in Compile).value.map { dir =>
val sv = scalaVersion.value
CrossVersion.partialVersion(sv) match {
case Some((2, 13)) => file(dir.getPath ++ "-2.13")
case _ => file(dir.getPath ++ "-2.11-2.12")
}
}
},
headerLicense := Some(HeaderLicense.Custom(
s"""|Scala (https://www.scala-lang.org)
|
|Copyright EPFL and Lightbend, Inc.
|
|Licensed under Apache License 2.0
|(http://www.apache.org/licenses/LICENSE-2.0).
|
|See the NOTICE file distributed with this work for
|additional information regarding copyright ownership.
|""".stripMargin)),

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

apiMappings ++= scalaInstance.value.libraryJars.filter { file =>
file.getName.startsWith("scala-library") && file.getName.endsWith(".jar")
Expand All @@ -47,7 +65,7 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform)
file(jarPath)
-> url("http://docs.oracle.com/javase/8/docs/api")
)
} getOrElse {
}.getOrElse {
// If everything fails, jam in Java 11 modules.
Map(
file("/modules/java.base")
Expand All @@ -58,16 +76,20 @@ lazy val xml = crossProject(JSPlatform, JVMPlatform)
}
}
)
.settings(
inConfig(Compile)(configSettings) ++ inConfig(Test)(configSettings)
)
.jvmSettings(
OsgiKeys.exportPackage := Seq(s"scala.xml.*;version=${version.value}"),

libraryDependencies += "junit" % "junit" % "4.13" % "test",
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test",
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.9" % "test",
libraryDependencies += ("org.scala-lang" % "scala-compiler" % scalaVersion.value % "test").exclude("org.scala-lang.modules", s"scala-xml_${scalaBinaryVersion.value}")
libraryDependencies += "junit" % "junit" % "4.13.2" % Test,
libraryDependencies += "com.github.sbt" % "junit-interface" % "0.13.3" % Test,
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.12.0" % Test,
libraryDependencies += ("org.scala-lang" % "scala-compiler" % scalaVersion.value % Test).exclude("org.scala-lang.modules", s"scala-xml_${scalaBinaryVersion.value}")
)
.jsSettings(
crossScalaVersions := crossScalaVersions.value.filterNot(_.startsWith("2.11")),
// Scala.js cannot run forked tests
fork in Test := false
Test / fork := false
)
.jsConfigure(_.enablePlugins(ScalaJSJUnitPlugin))
.jsEnablePlugins(ScalaJSJUnitPlugin)
54 changes: 0 additions & 54 deletions build.sh

This file was deleted.

2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.3.13
sbt.version=1.9.2
12 changes: 6 additions & 6 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
val scalaJSVersion =
Option(System.getenv("SCALAJS_VERSION")).filter(_.nonEmpty).getOrElse("0.6.33")

addSbtPlugin("org.scala-lang.modules" % "sbt-scala-module" % "2.1.3")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion)
addSbtPlugin("org.scala-lang.modules" % "sbt-scala-module" % "3.1.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.2")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0")
109 changes: 65 additions & 44 deletions shared/src/main/scala/scala/xml/Utility.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package scala
package xml

import scala.annotation.tailrec
import scala.collection.mutable
import scala.language.implicitConversions
import scala.collection.Seq
Expand Down Expand Up @@ -187,9 +188,7 @@ object Utility extends AnyRef with parsing.TokenTests {
decodeEntities: Boolean = true,
preserveWhitespace: Boolean = false,
minimizeTags: Boolean = false): StringBuilder =
{
serialize(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, if (minimizeTags) MinimizeMode.Always else MinimizeMode.Never)
}

/**
* Serialize an XML Node to a StringBuilder.
Expand All @@ -206,35 +205,66 @@ object Utility extends AnyRef with parsing.TokenTests {
stripComments: Boolean = false,
decodeEntities: Boolean = true,
preserveWhitespace: Boolean = false,
minimizeTags: MinimizeMode.Value = MinimizeMode.Default): StringBuilder =
{
x match {
case c: Comment => if (!stripComments) c buildString sb; sb
case s: SpecialNode => s buildString sb
case g: Group =>
for (c <- g.nodes) serialize(c, g.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags); sb
case el: Elem =>
// print tag with namespace declarations
sb.append('<')
el.nameToString(sb)
if (el.attributes ne null) el.attributes.buildString(sb)
el.scope.buildString(sb, pscope)
if (el.child.isEmpty &&
(minimizeTags == MinimizeMode.Always ||
(minimizeTags == MinimizeMode.Default && el.minimizeEmpty))) {
// no children, so use short form: <xyz .../>
sb.append("/>")
} else {
// children, so use long form: <xyz ...>...</xyz>
sb.append('>')
sequenceToXML(el.child, el.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
sb.append("</")
el.nameToString(sb)
sb.append('>')
}
case _ => throw new IllegalArgumentException("Don't know how to serialize a " + x.getClass.getName)
}
minimizeTags: MinimizeMode.Value = MinimizeMode.Default
): StringBuilder = {
serializeImpl(List(x), pscope, false, stripComments, minimizeTags, sb)
sb
}

private def serializeImpl(
ns: Seq[Node],
pscope: NamespaceBinding,
spaced: Boolean,
stripComments: Boolean,
minimizeTags: MinimizeMode.Value,
sb: StringBuilder
): Unit = {
@tailrec def ser(nss: List[List[Node]], pscopes: List[NamespaceBinding], spaced: List[Boolean], toClose: List[Node]): Unit = nss match {
case List(Nil) =>
case Nil :: rests =>
if (toClose.head != null) {
sb.append("</")
toClose.head.nameToString(sb)
sb.append('>')
}
ser(rests, pscopes.tail, spaced.tail, toClose.tail)
case (n :: ns) :: r =>
def sp(): Unit = if (ns.nonEmpty && spaced.head) sb.append(' ')
n match {
case c: Comment =>
if (!stripComments) {
c.buildString(sb)
sp()
}
ser(ns :: r, pscopes, spaced, toClose)
case s: SpecialNode =>
s.buildString(sb)
sp()
ser(ns :: r, pscopes, spaced, toClose)
case g: Group =>
ser(g.nodes.toList :: ns :: r, g.scope :: pscopes, false :: spaced, null :: toClose)
case e: Elem =>
sb.append('<')
e.nameToString(sb)
if (e.attributes.ne(null)) e.attributes.buildString(sb)
e.scope.buildString(sb, pscopes.head)
if (e.child.isEmpty &&
(minimizeTags == MinimizeMode.Always ||
(minimizeTags == MinimizeMode.Default && e.minimizeEmpty))) {
// no children, so use short form: <xyz .../>
sb.append("/>")
sp()
ser(ns :: r, pscopes, spaced, toClose)
} else {
sb.append('>')
val csp = e.child.forall(isAtomAndNotText)
ser(e.child.toList :: ns :: r, e.scope :: pscopes, csp :: spaced, e :: toClose)
}
case n => throw new IllegalArgumentException("Don't know how to serialize a " + n.getClass.getName)
}
}
ser(List(ns.toList), List(pscope), List(spaced), Nil)
}

def sequenceToXML(
children: Seq[Node],
Expand All @@ -243,20 +273,11 @@ object Utility extends AnyRef with parsing.TokenTests {
stripComments: Boolean = false,
decodeEntities: Boolean = true,
preserveWhitespace: Boolean = false,
minimizeTags: MinimizeMode.Value = MinimizeMode.Default): Unit =
{
if (children.isEmpty) return
else if (children forall isAtomAndNotText) { // add space
val it = children.iterator
val f = it.next()
serialize(f, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
while (it.hasNext) {
val x = it.next()
sb.append(' ')
serialize(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
}
} else children foreach { serialize(_, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) }
}
minimizeTags: MinimizeMode.Value = MinimizeMode.Default
): Unit = if (children.nonEmpty) {
val spaced = children.forall(isAtomAndNotText)
serializeImpl(children, pscope, spaced, stripComments, minimizeTags, sb)
}

/**
* Returns prefix of qualified name if any.
Expand Down
Loading

0 comments on commit 8f697e5

Please sign in to comment.