├── .jvmopts ├── project ├── build.properties └── plugins.sbt ├── jvm └── src │ └── test │ ├── resources │ └── scala │ │ └── xml │ │ ├── utf8.xml │ │ ├── archive │ │ ├── books │ │ │ └── book │ │ │ │ ├── author │ │ │ │ └── volume │ │ │ │ │ └── 1.xml │ │ │ │ └── author.xml │ │ └── books.xml │ │ ├── includee.xml │ │ ├── utf16.xml │ │ ├── site.xml │ │ └── includer.xml │ ├── scala │ └── scala │ │ └── xml │ │ ├── JavaByteSerialization.scala │ │ ├── XMLSyntaxTest.scala │ │ ├── SerializationTest.scala │ │ ├── AttributeTest.scala │ │ ├── parsing │ │ ├── Ticket0632Test.scala │ │ ├── PiParsingTest.scala │ │ ├── XhtmlParserTest.scala │ │ └── ConstructingParserTest.scala │ │ ├── BillionLaughsAttackTest.scala │ │ └── ReuseNodesTest.scala │ └── scala-2.x │ └── scala │ └── xml │ └── XMLTestJVM2x.scala ├── .github ├── dependabot.yml └── workflows │ ├── cla.yml │ ├── ci.yml │ └── release.yml ├── CONTRIBUTING.md ├── shared └── src │ ├── test │ ├── scala │ │ └── scala │ │ │ └── xml │ │ │ ├── JUnitAssertsForXML.scala │ │ │ ├── NodeBufferTest.scala │ │ │ ├── XMLEmbeddingTest.scala │ │ │ ├── parsing │ │ │ ├── PiParsingTest.scala │ │ │ └── Ticket0632Test.scala │ │ │ ├── Properties.scala │ │ │ ├── CommentTest.scala │ │ │ ├── PCDataTest.scala │ │ │ ├── dtd │ │ │ └── DeclTest.scala │ │ │ ├── PrintEmptyElementsTest.scala │ │ │ ├── MetaDataTest.scala │ │ │ ├── XMLSyntaxTest.scala │ │ │ ├── ShouldCompile.scala │ │ │ ├── NodeSeqTest.scala │ │ │ └── PatternMatchingTest.scala │ └── scala-2.x │ │ └── scala │ │ └── xml │ │ ├── ShouldCompile.scala │ │ ├── XMLTest2x.scala │ │ └── TransformersTest.scala │ └── main │ ├── scala │ └── scala │ │ └── xml │ │ ├── TypeSymbol.scala │ │ ├── MalformedAttributeException.scala │ │ ├── parsing │ │ ├── FatalError.scala │ │ ├── ExternalSources.scala │ │ ├── DefaultMarkupHandler.scala │ │ ├── XhtmlParser.scala │ │ ├── ConstructingHandler.scala │ │ ├── ConstructingParser.scala │ │ ├── NoBindingFactoryAdapter.scala │ │ ├── TokenTests.scala │ │ ├── MarkupHandler.scala │ │ └── XhtmlEntities.scala │ │ ├── transform │ │ ├── NestingTransformer.scala │ │ ├── RuleTransformer.scala │ │ ├── RewriteRule.scala │ │ └── BasicTransformer.scala │ │ ├── include │ │ ├── UnavailableResourceException.scala │ │ ├── CircularIncludeException.scala │ │ ├── XIncludeException.scala │ │ └── sax │ │ │ └── EncodingHeuristics.scala │ │ ├── dtd │ │ ├── impl │ │ │ ├── SyntaxError.scala │ │ │ ├── DetWordAutom.scala │ │ │ ├── WordExp.scala │ │ │ ├── Base.scala │ │ │ ├── Inclusion.scala │ │ │ ├── NondetWordAutom.scala │ │ │ ├── BaseBerrySethi.scala │ │ │ └── SubsetConstruction.scala │ │ ├── Tokens.scala │ │ ├── DTD.scala │ │ ├── DocType.scala │ │ ├── ValidationException.scala │ │ ├── ExternalID.scala │ │ └── ContentModel.scala │ │ ├── QNode.scala │ │ ├── TopScope.scala │ │ ├── SpecialNode.scala │ │ ├── Unparsed.scala │ │ ├── Text.scala │ │ ├── EntityRef.scala │ │ ├── ProcInstr.scala │ │ ├── Comment.scala │ │ ├── TextBuffer.scala │ │ ├── Atom.scala │ │ ├── PCData.scala │ │ ├── NodeBuffer.scala │ │ ├── Group.scala │ │ ├── factory │ │ ├── NodeFactory.scala │ │ └── XMLLoader.scala │ │ ├── PrefixedAttribute.scala │ │ ├── UnprefixedAttribute.scala │ │ ├── Null.scala │ │ ├── package.scala │ │ ├── NamespaceBinding.scala │ │ ├── Xhtml.scala │ │ ├── Document.scala │ │ ├── Attribute.scala │ │ ├── Elem.scala │ │ ├── Equality.scala │ │ └── XML.scala │ ├── scala-2 │ └── scala │ │ └── xml │ │ └── ScalaVersionSpecificReturnTypes.scala │ ├── scala-2.12 │ └── scala │ │ └── xml │ │ └── ScalaVersionSpecific.scala │ ├── scala-3 │ └── scala │ │ └── xml │ │ └── ScalaVersionSpecificReturnTypes.scala │ └── scala-2.13+ │ └── scala │ └── xml │ └── ScalaVersionSpecific.scala ├── CODE_OF_CONDUCT.md ├── NOTICE ├── .gitignore ├── .mailmap ├── CHANGELOG.md ├── README.md └── .circleci └── config.yml /.jvmopts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.7 2 | -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/utf8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/archive/books/book/author/volume/1.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/includee.xml: -------------------------------------------------------------------------------- 1 | 2 | Blah! 3 | 4 | -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/utf16.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala/scala-xml/HEAD/jvm/src/test/resources/scala/xml/utf16.xml -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/includer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/archive/books.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /jvm/src/test/resources/scala/xml/archive/books/book/author.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Please see the wiki for the scala-xml contributor guide: 5 | 6 | https://github.com/scala/scala-xml/wiki/Contributor-guide 7 | 8 | Thank you, 9 | 10 | The Scala XML maintainers 11 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/JUnitAssertsForXML.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | object JUnitAssertsForXML { 4 | 5 | private[xml] def assertEquals(expected: String, actual: NodeSeq): Unit = 6 | org.junit.Assert.assertEquals(expected, actual.toString) 7 | } 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | all repositories in these organizations: 2 | 3 | * [scala](https://github.com/scala) 4 | * [scalacenter](https://github.com/scalacenter) 5 | * [lampepfl](https://github.com/lampepfl) 6 | 7 | are covered by the Scala Code of Conduct: https://scala-lang.org/conduct/ 8 | -------------------------------------------------------------------------------- /shared/src/test/scala-2.x/scala/xml/ShouldCompile.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | // these tests depend on xml, so they ended up here, 4 | // though really they are compiler tests 5 | 6 | // t1626 7 | object o { 8 | val n: Elem = 9 | n.namespace == null 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/cla.yml: -------------------------------------------------------------------------------- 1 | name: "Check Scala CLA" 2 | on: 3 | pull_request: 4 | jobs: 5 | cla-check: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Verify CLA 9 | uses: scala/cla-checker@v1 10 | with: 11 | author: ${{ github.event.pull_request.user.login }} 12 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/NodeBufferTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.assertEquals 5 | 6 | class NodeBufferTest { 7 | 8 | @Test 9 | def testToString(): Unit = { 10 | val nodeBuffer: NodeBuffer = new NodeBuffer 11 | assertEquals("NodeBuffer()", nodeBuffer.toString) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-lang.modules" % "sbt-scala-module" % "3.4.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.20.1") 5 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.9") 6 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/TypeSymbol.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | abstract class TypeSymbol 17 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/XMLEmbeddingTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.assertEquals 5 | 6 | class XMLEmbeddingTest { 7 | 8 | @Test 9 | def basic(): Unit = { 10 | val ya: Elem = {{ 11 | assertEquals("{", ya.text) 12 | val ua: Elem = }} 13 | assertEquals("}", ua.text) 14 | val za: Elem = {{}}{{}}{{}} 15 | assertEquals("{}{}{}", za.text) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/MalformedAttributeException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | case class MalformedAttributeException(msg: String) extends RuntimeException(msg) 17 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/parsing/PiParsingTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml.parsing 2 | 3 | import org.junit.Test 4 | import scala.xml.JUnitAssertsForXML.assertEquals 5 | 6 | class PiParsingTest { 7 | 8 | @Test 9 | def piNoWSLiteral(): Unit = { 10 | val expected: String = "ab" 11 | assertEquals(expected, ab) 12 | } 13 | 14 | @Test 15 | def piLiteral(): Unit = { 16 | val expected: String = " a b " 17 | assertEquals(expected, a b ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/JavaByteSerialization.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import java.io.Serializable 4 | import org.apache.commons.lang3.SerializationUtils 5 | 6 | object JavaByteSerialization { 7 | def roundTrip[T <: Serializable](obj: T): T = { 8 | SerializationUtils.roundtrip(obj) 9 | } 10 | 11 | def serialize[T <: Serializable](in: T): Array[Byte] = { 12 | SerializationUtils.serialize(in) 13 | } 14 | 15 | def deserialize[T <: Serializable](in: Array[Byte]): T = { 16 | SerializationUtils.deserialize(in) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/FatalError.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | /** 18 | * !!! This is poorly named, but I guess it's in the API. 19 | */ 20 | case class FatalError(msg: String) extends java.lang.RuntimeException(msg) 21 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | scala-xml 2 | Copyright (c) 2002-2025 EPFL 3 | Copyright (c) 2011-2025 Lightbend, Inc. dba Akka 4 | 5 | scala-xml includes software developed at 6 | LAMP/EPFL (https://lamp.epfl.ch/) and 7 | Akka (https://akka.io/). 8 | 9 | Licensed under the Apache License, Version 2.0 (the "License"). 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/Properties.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | object Properties extends util.PropertiesTrait { 17 | override protected def propCategory: String = "scala-xml" 18 | override protected def pickJarBasedOn: Class[Node] = classOf[Node] 19 | } 20 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/transform/NestingTransformer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package transform 16 | 17 | import scala.collection.Seq 18 | 19 | class NestingTransformer(rule: RewriteRule) extends BasicTransformer { 20 | override def transform(n: Node): Seq[Node] = 21 | rule.transform(super.transform(n)) 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 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, 21] 13 | scala: [2.12.x, 2.13.x, 3.x] 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v6 17 | with: 18 | fetch-depth: 0 19 | - uses: actions/setup-java@v5 20 | with: 21 | distribution: temurin 22 | java-version: ${{matrix.java}} 23 | cache: sbt 24 | - uses: sbt/setup-sbt@v1 25 | - name: Test 26 | run: sbt ++${{matrix.scala}} test headerCheck versionPolicyCheck package 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: ["*"] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v6 10 | with: 11 | fetch-depth: 0 12 | - uses: actions/setup-java@v5 13 | with: 14 | distribution: temurin 15 | java-version: 8 16 | - uses: sbt/setup-sbt@v1 17 | - run: sbt versionCheck ci-release 18 | env: 19 | PGP_PASSPHRASE: ${{secrets.PGP_PASSPHRASE}} 20 | PGP_SECRET: ${{secrets.PGP_SECRET}} 21 | SONATYPE_PASSWORD: ${{secrets.SONATYPE_PASSWORD}} 22 | SONATYPE_USERNAME: ${{secrets.SONATYPE_USERNAME}} 23 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/include/UnavailableResourceException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package include 16 | 17 | /** 18 | * An `UnavailableResourceException` is thrown when an included document 19 | * cannot be found or loaded. 20 | */ 21 | class UnavailableResourceException(message: String) 22 | extends XIncludeException(message) { 23 | def this() = this(null) 24 | } 25 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/SyntaxError.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | /** 17 | * This runtime exception is thrown if an attempt to instantiate a 18 | * syntactically incorrect expression is detected. 19 | * 20 | * @author Burak Emir 21 | */ 22 | @deprecated("This class will be removed", "2.10.0") 23 | private[dtd] class SyntaxError(e: String) extends RuntimeException(e) 24 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/QNode.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * This object provides an extractor method to match a qualified node with 18 | * its namespace URI 19 | * 20 | * @author Burak Emir 21 | */ 22 | object QNode { 23 | def unapplySeq(n: Node): Some[(String, String, MetaData, ScalaVersionSpecific.SeqOfNode)] = 24 | Some((n.scope.getURI(n.prefix), n.label, n.attributes, n.child)) 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/CommentTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | final class CommentTest { 7 | 8 | @Test(expected=classOf[IllegalArgumentException]) 9 | def invalidCommentWithTwoDashes(): Unit = { 10 | Comment("invalid--comment") 11 | } 12 | 13 | @Test(expected=classOf[IllegalArgumentException]) 14 | def invalidCommentWithFinalDash(): Unit = { 15 | Comment("invalid comment-") 16 | } 17 | 18 | @Test 19 | def validCommentWithDash(): Unit = { 20 | val valid: String = "valid-comment" 21 | assertEquals(s"", Comment(valid).toString) 22 | } 23 | 24 | @Test 25 | def validEmptyComment(): Unit = { 26 | val valid: String = "" 27 | assertEquals(s"", Comment(valid).toString) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/PCDataTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.assertEquals 5 | 6 | class PCDataTest { 7 | 8 | def check(pcdata: String, expected: String): Unit = { 9 | val actual: PCData = new PCData(pcdata) 10 | assertEquals(expected, actual.toString) 11 | } 12 | 13 | @Test 14 | def emptyTest(): Unit = check("", "") 15 | 16 | @Test 17 | def bracketTest(): Unit = check("[]", "") 18 | 19 | @Test 20 | def hellaBracketingTest(): Unit = check("[[[[[[[[]]]]]]]]", "") 21 | 22 | @Test 23 | def simpleNestingTest(): Unit = check("]]>", "]]>") 24 | 25 | @Test 26 | def recursiveNestingTest(): Unit = check("", "]]>") 27 | } 28 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/parsing/Ticket0632Test.scala: -------------------------------------------------------------------------------- 1 | package scala.xml.parsing 2 | 3 | import org.junit.Test 4 | import scala.xml.JUnitAssertsForXML.assertEquals 5 | 6 | class Ticket0632Test { 7 | 8 | @Test 9 | def singleAmp(): Unit = { 10 | val expected: String = "" 11 | assertEquals(expected, ) 12 | assertEquals(expected, ) 13 | } 14 | 15 | @Test 16 | def oneAndHalfAmp(): Unit = { 17 | val expected: String = "" 18 | assertEquals(expected, ) 19 | assertEquals(expected, ) 20 | } 21 | 22 | @Test 23 | def doubleAmp(): Unit = { 24 | val expected: String = "" 25 | assertEquals(expected, ) 26 | assertEquals(expected, ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/include/CircularIncludeException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package include 16 | 17 | /** 18 | * A `CircularIncludeException` is thrown when an included document attempts 19 | * to include itself or one of its ancestor documents. 20 | */ 21 | class CircularIncludeException(message: String) extends XIncludeException { 22 | 23 | /** 24 | * Constructs a `CircularIncludeException` with `'''null'''`. 25 | * as its error detail message. 26 | */ 27 | def this() = this(null) 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Are you tempted to edit this file? 3 | # 4 | # First consider if the changes make sense for all, 5 | # or if they are specific to your workflow/system. 6 | # If it is the latter, you can augment this list with 7 | # entries in .git/info/excludes 8 | # 9 | # see also test/files/.gitignore 10 | # 11 | 12 | *.jar 13 | *~ 14 | 15 | # target directories for ant build 16 | /build/ 17 | /dists/ 18 | 19 | # other 20 | /out/ 21 | /bin/ 22 | /sandbox/ 23 | 24 | # eclipse, intellij 25 | /.classpath 26 | /.project 27 | /src/intellij/*.iml 28 | /src/intellij/*.ipr 29 | /src/intellij/*.iws 30 | /.cache 31 | /.idea 32 | /.settings 33 | 34 | # bak files produced by ./cleanup-commit 35 | *.bak 36 | 37 | # Standard symbolic link to build/quick/bin 38 | qbin 39 | 40 | # Mac specific, but that is common enough a dev platform to warrant inclusion. 41 | .DS_Store 42 | 43 | target/ -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/transform/RuleTransformer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package transform 16 | 17 | import scala.collection.Seq 18 | 19 | class RuleTransformer(rules: RewriteRule*) extends BasicTransformer { 20 | private val transformers: Seq[NestingTransformer] = rules.map(new NestingTransformer(_)) 21 | 22 | override def transform(n: Node): Seq[Node] = 23 | if (transformers.isEmpty) n 24 | else transformers.tail.foldLeft(transformers.head.transform(n)) { (res, transformer) => transformer.transform(res) } 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/transform/RewriteRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package transform 16 | 17 | import scala.collection.Seq 18 | 19 | /** 20 | * A RewriteRule, when applied to a term, yields either 21 | * the result of rewriting the term or the term itself if the rule 22 | * is not applied. 23 | * 24 | * @author Burak Emir 25 | */ 26 | abstract class RewriteRule extends BasicTransformer { 27 | override def transform(ns: Seq[Node]): Seq[Node] = super.transform(ns) 28 | override def transform(n: Node): Seq[Node] = n 29 | } 30 | 31 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/XMLSyntaxTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.assertEquals 5 | 6 | class XMLSyntaxTestJVM { 7 | 8 | @Test 9 | def test3(): Unit = { 10 | // this demonstrates how to handle entities 11 | val s: io.Source = io.Source.fromString(" ") 12 | object parser extends xml.parsing.ConstructingParser(s, preserveWS = false /*ignore ws*/) { 13 | override def replacementText(entityName: String): io.Source = { 14 | entityName match { 15 | case "nbsp" => io.Source.fromString("\u0160") 16 | case _ => super.replacementText(entityName) 17 | } 18 | } 19 | nextch() // !!important, to initialize the parser 20 | } 21 | val parsed: NodeSeq = parser.element(TopScope) // parse the source as element 22 | // alternatively, we could call document() 23 | assertEquals("Š", parsed.toString) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/SerializationTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import scala.collection.Seq 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | class SerializationTest { 8 | @Test 9 | def xmlLiteral(): Unit = { 10 | val n: Elem = 11 | assertEquals(n, JavaByteSerialization.roundTrip(n)) 12 | } 13 | 14 | @Test 15 | def empty(): Unit = { 16 | assertEquals(NodeSeq.Empty, JavaByteSerialization.roundTrip(NodeSeq.Empty)) 17 | } 18 | 19 | @Test 20 | def unmatched(): Unit = { 21 | assertEquals(NodeSeq.Empty, JavaByteSerialization.roundTrip( \ "HTML")) 22 | } 23 | 24 | @Test 25 | def implicitConversion(): Unit = { 26 | val parent: Elem = 27 | val children: Seq[Node] = parent.child 28 | val asNodeSeq: NodeSeq = children // implicit seqToNodeSeq 29 | assertEquals(asNodeSeq, JavaByteSerialization.roundTrip(asNodeSeq)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/test/scala-2.x/scala/xml/XMLTest2x.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.{Test => UnitTest} 4 | import org.junit.Assert.assertTrue 5 | import org.junit.Assert.assertEquals 6 | 7 | class XMLTest2x { 8 | // t-486 9 | def wsdlTemplate3(serviceName: String): Node = 10 | 11 | 12 | 13 | @UnitTest 14 | def wsdl(): Unit = { 15 | assertEquals(""" 16 | """, wsdlTemplate3("service3").toString) 17 | } 18 | 19 | @UnitTest 20 | def t5154(): Unit = { 21 | 22 | // extra space made the pattern OK 23 | def f: Boolean = {{3}} match { case {{3}} => true } 24 | 25 | // lack of space used to error: illegal start of simple pattern 26 | def g: Boolean = {{3}} match { case {{3}} => true } 27 | 28 | assertTrue(f) 29 | assertTrue(g) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/dtd/DeclTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | package dtd 3 | 4 | import org.junit.Test 5 | import org.junit.Assert.assertEquals 6 | 7 | class DeclTest { 8 | 9 | @Test 10 | def elemDeclToString(): Unit = { 11 | assertEquals( 12 | "", 13 | ElemDecl("x", PCDATA).toString 14 | ) 15 | } 16 | 17 | @Test 18 | def attListDeclToString(): Unit = { 19 | 20 | val expected: String = 21 | """|""".stripMargin 24 | 25 | val actual: String = AttListDecl("x", 26 | List( 27 | AttrDecl("y", "CDATA", REQUIRED), 28 | AttrDecl("z", "CDATA", REQUIRED) 29 | ) 30 | ).toString 31 | 32 | assertEquals(expected, actual) 33 | } 34 | 35 | @Test 36 | def parsedEntityDeclToString(): Unit = { 37 | assertEquals( 38 | """""", 39 | ParsedEntityDecl("foo", ExtDef(SystemID("bar"))).toString 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/ExternalSources.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | import java.net.URL 18 | import java.io.File.separator 19 | 20 | import scala.io.Source 21 | 22 | /** 23 | * @author Burak Emir 24 | */ 25 | trait ExternalSources { 26 | self: ExternalSources with MarkupParser with MarkupHandler => 27 | 28 | def externalSource(systemId: String): Source = { 29 | if (systemId.startsWith("http:")) 30 | return Source.fromURL(new URL(systemId)) 31 | 32 | val fileStr: String = input.descr match { 33 | case x if x.startsWith("file:") => x.drop(5) 34 | case x => x.take(x.lastIndexOf(separator) + 1) 35 | } 36 | 37 | Source.fromFile(s"$fileStr$systemId") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/TopScope.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * top level namespace scope. only contains the predefined binding 18 | * for the "xml" prefix which is bound to 19 | * "http://www.w3.org/XML/1998/namespace" 20 | */ 21 | object TopScope extends NamespaceBinding(null, null, null) { 22 | 23 | override def getURI(prefix1: String): String = 24 | if (prefix1 == XML.xml) XML.namespace else null 25 | 26 | override def getPrefix(uri1: String): String = 27 | if (uri1 == XML.namespace) XML.xml else null 28 | 29 | override def toString: String = "" 30 | 31 | override def buildString(stop: NamespaceBinding): String = "" 32 | override def buildString(sb: StringBuilder, ignore: NamespaceBinding): Unit = () 33 | } 34 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/DefaultMarkupHandler.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | /** Default implementation of markup handler always returns `NodeSeq.Empty` */ 18 | abstract class DefaultMarkupHandler extends MarkupHandler { 19 | 20 | override def elem(pos: Int, pre: String, label: String, attrs: MetaData, 21 | scope: NamespaceBinding, empty: Boolean, args: NodeSeq): NodeSeq = NodeSeq.Empty 22 | 23 | override def procInstr(pos: Int, target: String, txt: String): NodeSeq = NodeSeq.Empty 24 | 25 | override def comment(pos: Int, comment: String): NodeSeq = NodeSeq.Empty 26 | 27 | override def entityRef(pos: Int, n: String): NodeSeq = NodeSeq.Empty 28 | 29 | override def text(pos: Int, txt: String): NodeSeq = NodeSeq.Empty 30 | } 31 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/XhtmlParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | import scala.io.Source 18 | 19 | /** 20 | * An XML Parser that preserves `CDATA` blocks and knows about 21 | * [[scala.xml.parsing.XhtmlEntities]]. 22 | * 23 | * @author (c) David Pollak, 2007 WorldWide Conferencing, LLC. 24 | */ 25 | class XhtmlParser(override val input: Source) extends ConstructingHandler with MarkupParser with ExternalSources { 26 | override val preserveWS: Boolean = true 27 | ent ++= XhtmlEntities() 28 | } 29 | 30 | /** 31 | * Convenience method that instantiates, initializes and runs an `XhtmlParser`. 32 | * 33 | * @author Burak Emir 34 | */ 35 | object XhtmlParser { 36 | def apply(source: Source): NodeSeq = new XhtmlParser(source).initialize.document() 37 | } 38 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/SpecialNode.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * `SpecialNode` is a special XML node which represents either text 18 | * `(PCDATA)`, a comment, a `PI`, or an entity ref. 19 | * 20 | * @author Burak Emir 21 | */ 22 | abstract class SpecialNode extends Node { 23 | 24 | /** always empty */ 25 | final override def attributes: Null.type = Null 26 | 27 | /** always Node.EmptyNamespace - TODO not really: Node.EmptyNamespace is "", but this is null. */ 28 | final override def namespace: scala.Null = null 29 | 30 | /** always empty */ 31 | final override def child: ScalaVersionSpecificReturnTypes.SpecialNodeChild = Nil 32 | 33 | /** Append string representation to the given string buffer argument. */ 34 | def buildString(sb: StringBuilder): StringBuilder 35 | } 36 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/Tokens.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package dtd 16 | 17 | class Tokens { 18 | 19 | // Tokens 20 | 21 | final val TOKEN_PCDATA: Int = 0 22 | final val NAME: Int = 1 23 | final val LPAREN: Int = 3 24 | final val RPAREN: Int = 4 25 | final val COMMA: Int = 5 26 | final val STAR: Int = 6 27 | final val PLUS: Int = 7 28 | final val OPT: Int = 8 29 | final val CHOICE: Int = 9 30 | final val END: Int = 10 31 | final val S: Int = 13 32 | 33 | final def token2string(i: Int): String = i match { 34 | case 0 => "#PCDATA" 35 | case 1 => "NAME" 36 | case 3 => "(" 37 | case 4 => ")" 38 | case 5 => "," 39 | case 6 => "*" 40 | case 7 => "+" 41 | case 8 => "?" 42 | case 9 => "|" 43 | case 10 => "END" 44 | case 13 => " " 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/DTD.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package dtd 16 | 17 | import scala.collection.mutable 18 | import scala.collection.Seq 19 | 20 | /** 21 | * A document type declaration. 22 | * 23 | * @author Burak Emir 24 | */ 25 | abstract class DTD { 26 | var externalID: ExternalID = _ 27 | var decls: List[Decl] = Nil 28 | def notations: Seq[NotationDecl] = Nil 29 | def unparsedEntities: Seq[EntityDecl] = Nil 30 | 31 | var elem: mutable.Map[String, ElemDecl] = new mutable.HashMap[String, ElemDecl]() 32 | var attr: mutable.Map[String, AttListDecl] = new mutable.HashMap[String, AttListDecl]() 33 | var ent: mutable.Map[String, EntityDecl] = new mutable.HashMap[String, EntityDecl]() 34 | 35 | override def toString: String = 36 | s"DTD ${Option(externalID).getOrElse("")} [\n${decls.mkString("\n")}\n]" 37 | } 38 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/AttributeTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Assert.assertNotEquals 6 | 7 | class AttributeTestJVM { 8 | 9 | @Test 10 | def attributeOrder(): Unit = { 11 | val x: Elem = 12 | assertEquals("""""", x.toString) 13 | } 14 | 15 | @Test 16 | def attributesFromString(): Unit = { 17 | val str: String = """""" 18 | val doc: Elem = XML.loadString(str) 19 | assertEquals(str, doc.toString) 20 | } 21 | 22 | @Test 23 | def attributesAndNamespaceFromString(): Unit = { 24 | val str: String = """""" 25 | val doc: Elem = XML.loadString(str) 26 | assertNotEquals(str, doc.toString) 27 | val str2: String = """""" 28 | val doc2: Elem = XML.loadString(str2) 29 | assertEquals(str2, doc2.toString) 30 | } 31 | 32 | @Test(expected=classOf[SAXParseException]) 33 | def attributesFromStringWithDuplicate(): Unit = { 34 | val str: String = """""" 35 | XML.loadString(str) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/ConstructingHandler.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | /** 18 | * Implementation of MarkupHandler that constructs nodes. 19 | * 20 | * @author Burak Emir 21 | */ 22 | abstract class ConstructingHandler extends MarkupHandler { 23 | val preserveWS: Boolean 24 | 25 | override def elem(pos: Int, pre: String, label: String, attrs: MetaData, 26 | pscope: NamespaceBinding, empty: Boolean, nodes: NodeSeq): NodeSeq = 27 | Elem(pre, label, attrs, pscope, empty, nodes: _*) 28 | 29 | override def procInstr(pos: Int, target: String, txt: String): ProcInstr = ProcInstr(target, txt) 30 | override def comment(pos: Int, txt: String): Comment = Comment(txt) 31 | override def entityRef(pos: Int, n: String): EntityRef = EntityRef(n) 32 | override def text(pos: Int, txt: String): Text = Text(txt) 33 | } 34 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/parsing/Ticket0632Test.scala: -------------------------------------------------------------------------------- 1 | package scala.xml.parsing 2 | 3 | import org.junit.Test 4 | import scala.xml.JUnitAssertsForXML.assertEquals 5 | import scala.xml.NodeSeq 6 | 7 | class Ticket0632TestJVM { 8 | 9 | import scala.io.Source.fromString 10 | import scala.xml.parsing.ConstructingParser.fromSource 11 | import scala.xml.TopScope 12 | private def parse(s:String): NodeSeq = fromSource(fromString(s), preserveWS = false).element(TopScope) 13 | 14 | @Test 15 | def singleAmp(): Unit = { 16 | val expected: String = "" 17 | assertEquals(expected, parse("")) 18 | assertEquals(expected, xml.XML.loadString("")) 19 | } 20 | 21 | @Test 22 | def oneAndHalfAmp(): Unit = { 23 | val expected: String = "" 24 | assertEquals(expected, xml.XML.loadString("")) 25 | assertEquals(expected, parse("")) 26 | } 27 | 28 | @Test 29 | def doubleAmp(): Unit = { 30 | val expected: String = "" 31 | assertEquals(expected, xml.XML.loadString("")) 32 | assertEquals(expected, parse("")) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Unparsed.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * An XML node for unparsed content. It will be output verbatim, all bets 18 | * are off regarding wellformedness etc. 19 | * 20 | * @author Burak Emir 21 | * @param data content in this node, may not be null. 22 | */ 23 | // Note: used by the Scala compiler. 24 | class Unparsed(data: String) extends Atom[String](data) { 25 | 26 | /** 27 | * Returns text, with some characters escaped according to XML 28 | * specification. 29 | */ 30 | override def buildString(sb: StringBuilder): StringBuilder = 31 | sb.append(data) 32 | } 33 | 34 | /** 35 | * This singleton object contains the `apply`and `unapply` methods for 36 | * convenient construction and deconstruction. 37 | * 38 | * @author Burak Emir 39 | */ 40 | object Unparsed { 41 | def apply(data: String): Unparsed = new Unparsed(data) 42 | def unapply(x: Unparsed): Some[String] = Some(x.data) 43 | } 44 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/parsing/PiParsingTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml.parsing 2 | 3 | import org.junit.Test 4 | import scala.xml.JUnitAssertsForXML.assertEquals 5 | import scala.xml.NodeSeq 6 | 7 | class PiParsingTestJVM { 8 | 9 | import scala.io.Source.fromString 10 | import scala.xml.parsing.ConstructingParser.fromSource 11 | import scala.xml.TopScope 12 | private def parse(s: String): NodeSeq = fromSource(fromString(s), preserveWS = true).element(TopScope) 13 | private def parseNoWS(s: String): NodeSeq = fromSource(fromString(s), preserveWS = false).element(TopScope) 14 | 15 | @Test 16 | def piNoWSparse(): Unit = { 17 | val expected: String = "ab" 18 | assertEquals(expected, parseNoWS("ab")) 19 | } 20 | 21 | @Test 22 | def piNoWSloadString(): Unit = { 23 | val expected: String = "ab" 24 | assertEquals(expected, xml.XML.loadString("ab")) 25 | } 26 | 27 | @Test 28 | def piParse(): Unit = { 29 | val expected: String = " a b " 30 | assertEquals(expected, parse(" a b ")) 31 | } 32 | 33 | @Test 34 | def piLoadString(): Unit = { 35 | val expected: String = " a b " 36 | assertEquals(expected, xml.XML.loadString(" a b ")) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Text.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * The class `Text` implements an XML node for text (PCDATA). 18 | * It is used in both non-bound and bound XML representations. 19 | * 20 | * @author Burak Emir 21 | * @param data the text contained in this node, may not be null. 22 | */ 23 | // Note: used by the Scala compiler. 24 | class Text(data: String) extends Atom[String](data) { 25 | 26 | /** 27 | * Returns text, with some characters escaped according to the XML 28 | * specification. 29 | */ 30 | override def buildString(sb: StringBuilder): StringBuilder = 31 | Utility.escape(data, sb) 32 | } 33 | 34 | /** 35 | * This singleton object contains the `apply`and `unapply` methods for 36 | * convenient construction and deconstruction. 37 | * 38 | * @author Burak Emir 39 | */ 40 | // Note: used by the Scala compiler. 41 | object Text { 42 | def apply(data: String): Text = new Text(data) 43 | def unapply(other: Any): Option[String] = other match { 44 | case x: Text => Some(x.data) 45 | case _ => None 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/EntityRef.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * The class `EntityRef` implements an XML node for entity references. 18 | * 19 | * @author Burak Emir 20 | * @param entityName the name of the entity reference, for example `amp`. 21 | */ 22 | // Note: used by the Scala compiler. 23 | case class EntityRef(entityName: String) extends SpecialNode { 24 | final override def doCollectNamespaces: Boolean = false 25 | final override def doTransform: Boolean = false 26 | override def label: String = "#ENTITY" 27 | 28 | override def text: String = entityName match { 29 | case "lt" => "<" 30 | case "gt" => ">" 31 | case "amp" => "&" 32 | case "apos" => "'" 33 | case "quot" => "\"" 34 | case _ => Utility.sbToString(buildString) 35 | } 36 | 37 | /** 38 | * Appends `"& entityName;"` to this string buffer. 39 | * 40 | * @param sb the string buffer. 41 | * @return the modified string buffer `sb`. 42 | */ 43 | override def buildString(sb: StringBuilder): StringBuilder = 44 | sb.append(s"&$entityName;") 45 | } 46 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/DocType.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package dtd 16 | 17 | import scala.collection.Seq 18 | 19 | /** 20 | * An XML node for document type declaration. 21 | * 22 | * @author Burak Emir 23 | * 24 | * @param name name of this DOCTYPE 25 | * @param extID NoExternalID or the external ID of this doctype 26 | * @param intSubset sequence of internal subset declarations 27 | */ 28 | case class DocType(name: String, extID: ExternalID, intSubset: Seq[dtd.Decl]) { 29 | if (!Utility.isName(name)) 30 | throw new IllegalArgumentException(s"$name must be an XML Name") 31 | 32 | /** returns "<!DOCTYPE + name + extID? + ("["+intSubSet+"]")? >" */ 33 | final override def toString: String = { 34 | def intString: String = 35 | if (intSubset.isEmpty) "" 36 | else intSubset.mkString("[", "", "]") 37 | 38 | s"" 39 | } 40 | } 41 | 42 | object DocType { 43 | /** Creates a doctype with no external id, nor internal subset declarations. */ 44 | def apply(name: String): DocType = apply(name, NoExternalID, Nil) 45 | } 46 | -------------------------------------------------------------------------------- /shared/src/main/scala-2/scala/xml/ScalaVersionSpecificReturnTypes.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.xml 14 | 15 | /* 16 | Unlike other Scala-version-specific things, this class is not filling any gaps in capabilities 17 | between different versions of Scala; instead, it mostly documents the types that different versions of the 18 | Scala compiler inferred in the unfortunate absence of the explicit type annotations. 19 | What should have been specified explicitly is given in the comments; 20 | next time we break binary compatibility the types should be changed in the code and this class removed. 21 | */ 22 | private[xml] object ScalaVersionSpecificReturnTypes { // should be 23 | type ExternalIDAttribute = MetaData // Null.type 24 | type NoExternalIDId = scala.Null 25 | type NodeNoAttributes = MetaData // Null.type 26 | type NullFilter = MetaData // Null.type 27 | type NullGetNamespace = scala.Null 28 | type NullNext = scala.Null 29 | type NullKey = scala.Null 30 | type NullValue = scala.Null 31 | type NullApply3 = scala.Null 32 | type NullRemove = Null.type 33 | type SpecialNodeChild = Nil.type 34 | type GroupChild = Nothing 35 | } 36 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/BillionLaughsAttackTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | 5 | class BillionLaughsAttackTest { 6 | 7 | /** 8 | * org.xml.sax.SAXParseException: JAXP00010001: The parser has 9 | * encountered more than "64000" entity expansions in this document; 10 | * this is the limit imposed by the JDK. 11 | */ 12 | @Test(expected=classOf[org.xml.sax.SAXParseException]) 13 | def lolzTest(): Unit = { 14 | XML.loadString(lolz) 15 | } 16 | 17 | // Copied from https://msdn.microsoft.com/en-us/magazine/ee335713.aspx 18 | val lolz: String = 19 | """ 20 | | 22 | | 23 | | 24 | | 25 | | 26 | | 27 | | 28 | | 29 | | 30 | | 31 | | 32 | |]> 33 | |&lol9; 34 | |""".stripMargin 35 | } 36 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/ProcInstr.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * an XML node for processing instructions (PI) 18 | * 19 | * @author Burak Emir 20 | * @param target target name of this PI 21 | * @param proctext text contained in this node, may not contain "?>" 22 | */ 23 | // Note: used by the Scala compiler. 24 | case class ProcInstr(target: String, proctext: String) extends SpecialNode { 25 | if (!Utility.isName(target)) 26 | throw new IllegalArgumentException(s"$target must be an XML Name") 27 | if (proctext.contains("?>")) 28 | throw new IllegalArgumentException(s"""$proctext may not contain "?>"""") 29 | if (target.toLowerCase == "xml") 30 | throw new IllegalArgumentException(s"$target is reserved") 31 | 32 | final override def doCollectNamespaces: Boolean = false 33 | final override def doTransform: Boolean = false 34 | 35 | final override def label: String = "#PI" 36 | override def text: String = "" 37 | 38 | /** 39 | * appends "<?" target (" "+text)?+"?>" 40 | * to this stringbuffer. 41 | */ 42 | override def buildString(sb: StringBuilder): StringBuilder = 43 | sb.append(s"") 44 | } 45 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/parsing/XhtmlParserTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | package parsing 3 | 4 | import scala.io.Source 5 | 6 | import org.junit.Test 7 | import org.junit.Assert.assertEquals 8 | 9 | class XhtmlParserTest { 10 | 11 | @Test 12 | def issue259(): Unit = { 13 | val xml: String = 14 | """| 15 | | 16 | | 17 | | 18 | | 19 | | 20 | |

Text

21 | | 22 | |""".stripMargin 23 | 24 | val expected: Elem = 25 | 26 | 27 | 28 | 29 |

Text

30 | 31 | 32 | 33 | assertEquals(expected, XhtmlParser(Source.fromString(xml)).theSeq) 34 | } 35 | 36 | @Test 37 | def html4Strict(): Unit = { 38 | val xml: String = 39 | """| 41 | | 42 | | 43 | | Title 44 | | 45 | | 46 | |

Text

47 | | 48 | |""".stripMargin 49 | 50 | val expected: Elem = 51 | 52 | Title 53 | 54 | 55 |

Text

56 | 57 | 58 | 59 | assertEquals(expected, XhtmlParser(Source.fromString(xml)).theSeq) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Comment.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * The class `Comment` implements an XML node for comments. 18 | * 19 | * @author Burak Emir 20 | * @param commentText the text contained in this node, may not contain "--" 21 | * and the final character may not be `-` to prevent a closing span of `-->` 22 | * which is invalid. [[https://www.w3.org/TR/xml11//#IDA5CES]] 23 | */ 24 | // Note: used by the Scala compiler. 25 | case class Comment(commentText: String) extends SpecialNode { 26 | 27 | override def label: String = "#REM" 28 | override def text: String = "" 29 | final override def doCollectNamespaces: Boolean = false 30 | final override def doTransform: Boolean = false 31 | 32 | if (commentText.contains("--")) 33 | throw new IllegalArgumentException(s"""text contains "--"""") 34 | 35 | if (commentText.nonEmpty && commentText.charAt(commentText.length - 1) == '-') 36 | throw new IllegalArgumentException("The final character of a XML comment may not be '-'. See https://www.w3.org/TR/xml11//#IDA5CES") 37 | 38 | /** 39 | * Appends "" to this string buffer. 40 | */ 41 | override def buildString(sb: StringBuilder): StringBuilder = 42 | sb.append(s"") 43 | } 44 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/TextBuffer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | import scala.collection.immutable.{Seq => ISeq} 18 | import Utility.isSpace 19 | 20 | object TextBuffer { 21 | def fromString(str: String): TextBuffer = new TextBuffer().append(str) 22 | } 23 | 24 | /** 25 | * The class `TextBuffer` is for creating text nodes without surplus 26 | * whitespace. All occurrences of one or more whitespace in strings 27 | * appended with the `append` method will be replaced by a single space 28 | * character, and leading and trailing space will be removed completely. 29 | */ 30 | class TextBuffer extends ScalaVersionSpecificTextBuffer { 31 | val sb: StringBuilder = new StringBuilder() 32 | 33 | /** 34 | * Appends this string to the text buffer, trimming whitespaces as needed. 35 | */ 36 | def append(cs: Seq[Char]): this.type = { 37 | cs.foreach { c => 38 | if (!isSpace(c)) sb.append(c) 39 | else if (sb.isEmpty || !isSpace(sb.last)) sb.append(' ') 40 | } 41 | this 42 | } 43 | 44 | /** 45 | * Returns an empty sequence if text is only whitespace. 46 | * 47 | * @return the text without whitespaces. 48 | */ 49 | def toText: ScalaVersionSpecific.SeqOfText = sb.toString.trim match { 50 | case "" => Nil 51 | case s => ISeq(Text(s)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/ValidationException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package dtd 16 | 17 | case class ValidationException(e: String) extends Exception(e) 18 | 19 | /** 20 | * @author Burak Emir 21 | */ 22 | object MakeValidationException { 23 | def fromFixedAttribute(k: String, value: String, actual: String): ValidationException = 24 | ValidationException(s"""value of attribute $k FIXED to "$value", but document tries "$actual"""") 25 | 26 | def fromNonEmptyElement(): ValidationException = 27 | ValidationException("element should be *empty*") 28 | 29 | def fromUndefinedElement(label: String): ValidationException = 30 | ValidationException(s"""element "$label" not allowed here""") 31 | 32 | def fromUndefinedAttribute(key: String): ValidationException = 33 | ValidationException(s"attribute $key not allowed here") 34 | 35 | def fromMissingAttribute(allKeys: Set[String]): ValidationException = { 36 | val sb: StringBuilder = new StringBuilder("missing value for REQUIRED attribute") 37 | if (allKeys.size > 1) sb.append('s') 38 | allKeys.foreach(k => sb.append(s"'$k'")) 39 | ValidationException(sb.toString) 40 | } 41 | 42 | def fromMissingAttribute(key: String, tpe: String): ValidationException = 43 | ValidationException(s"missing value for REQUIRED attribute $key of type $tpe") 44 | } 45 | -------------------------------------------------------------------------------- /shared/src/main/scala-2.12/scala/xml/ScalaVersionSpecific.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.xml 14 | 15 | import scala.collection.{SeqLike, mutable} 16 | import scala.collection.generic.CanBuildFrom 17 | 18 | private[xml] object ScalaVersionSpecific { 19 | import NodeSeq.Coll 20 | type CBF[-From, -A, +C] = CanBuildFrom[From, A, C] 21 | object NodeSeqCBF extends CanBuildFrom[Coll, Node, NodeSeq] { 22 | override def apply(from: Coll): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder 23 | override def apply(): mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder 24 | } 25 | type SeqOfNode = scala.collection.Seq[Node] 26 | type SeqOfText = scala.collection.Seq[Text] 27 | } 28 | 29 | private[xml] trait ScalaVersionSpecificNodeSeq extends SeqLike[Node, NodeSeq] { self: NodeSeq => 30 | /** Creates a list buffer as builder for this class */ 31 | override protected[this] def newBuilder: mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder 32 | } 33 | 34 | private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer => 35 | override def stringPrefix: String = "NodeBuffer" 36 | } 37 | 38 | private[xml] trait ScalaVersionSpecificNode { self: Node => } 39 | 40 | private[xml] trait ScalaVersionSpecificMetaData { self: MetaData => } 41 | 42 | private[xml] trait ScalaVersionSpecificTextBuffer { self: TextBuffer => } 43 | 44 | private[xml] trait ScalaVersionSpecificUtility { self: Utility.type => } 45 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Atom.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * The class `Atom` provides an XML node for text (`PCDATA`). 20 | * It is used in both non-bound and bound XML representations. 21 | * 22 | * @author Burak Emir 23 | * @param data the text contained in this node, may not be `'''null'''`. 24 | */ 25 | class Atom[+A](val data: A) extends SpecialNode with Serializable { 26 | if (data == null) 27 | throw new IllegalArgumentException(s"cannot construct ${getClass.getSimpleName} with null") 28 | 29 | override protected def basisForHashCode: Seq[Any] = Seq(data) 30 | 31 | override def strict_==(other: Equality): Boolean = other match { 32 | case x: Atom[?] => data == x.data 33 | case _ => false 34 | } 35 | 36 | override def canEqual(other: Any): Boolean = other match { 37 | case _: Atom[?] => true 38 | case _ => false 39 | } 40 | 41 | final override def doCollectNamespaces: Boolean = false 42 | final override def doTransform: Boolean = false 43 | 44 | override def label: String = "#PCDATA" 45 | 46 | /** 47 | * Returns text, with some characters escaped according to the XML 48 | * specification. 49 | */ 50 | override def buildString(sb: StringBuilder): StringBuilder = 51 | Utility.escape(data.toString, sb) 52 | 53 | override def text: String = data.toString 54 | } 55 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/PCData.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * This class (which is not used by all XML parsers, but always used by the 18 | * XHTML one) represents parseable character data, which appeared as CDATA 19 | * sections in the input and is to be preserved as CDATA section in the output. 20 | * 21 | * @author Burak Emir 22 | */ 23 | // Note: used by the Scala compiler (before Scala 3). 24 | class PCData(data: String) extends Atom[String](data) { 25 | 26 | /** 27 | * Returns text, with some characters escaped according to the XML 28 | * specification. 29 | * 30 | * @param sb the input string buffer associated to some XML element 31 | * @return the input string buffer with the formatted CDATA section 32 | */ 33 | override def buildString(sb: StringBuilder): StringBuilder = { 34 | val dataStr: String = data.replaceAll("]]>", "]]]]>") 35 | sb.append(s"") 36 | } 37 | } 38 | 39 | /** 40 | * This singleton object contains the `apply`and `unapply` methods for 41 | * convenient construction and deconstruction. 42 | * 43 | * @author Burak Emir 44 | */ 45 | // Note: used by the Scala compiler (before Scala 3). 46 | object PCData { 47 | def apply(data: String): PCData = new PCData(data) 48 | def unapply(other: Any): Option[String] = other match { 49 | case x: PCData => Some(x.data) 50 | case _ => None 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/DetWordAutom.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | /** 17 | * A deterministic automaton. States are integers, where 18 | * 0 is always the only initial state. Transitions are represented 19 | * in the delta function. A default transitions is one that 20 | * is taken when no other transition can be taken. 21 | * All states are reachable. Accepting states are those for which 22 | * the partial function 'finals' is defined. 23 | * 24 | * @author Burak Emir 25 | */ 26 | // TODO: still used in ContentModel -- @deprecated("This class will be removed", "2.10.0") 27 | private[dtd] abstract class DetWordAutom[T <: AnyRef] { 28 | val nstates: Int 29 | val finals: Array[Int] 30 | val delta: Array[scala.collection.mutable.Map[T, Int]] 31 | val default: Array[Int] 32 | 33 | def isFinal(q: Int): Boolean = finals(q) != 0 34 | def isSink(q: Int): Boolean = delta(q).isEmpty && default(q) == q 35 | def next(q: Int, label: T): Int = delta(q).getOrElse(label, default(q)) 36 | 37 | override def toString: String = { 38 | val map: Map[Int, Int] = finals.zipWithIndex.map(_.swap).toMap 39 | val sb: StringBuilder = new StringBuilder(s"[DetWordAutom nstates=$nstates finals=$map delta=\n") 40 | 41 | for (i <- 0.until(nstates)) { 42 | sb.append(s"$i->${delta(i)}\n") 43 | if (i < default.length) 44 | sb.append(s"_>${default(i)}\n") 45 | } 46 | sb.toString 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/WordExp.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | /** 17 | * The class `WordExp` provides regular word expressions. 18 | * 19 | * Users have to instantiate type member `_regexpT <;: RegExp` 20 | * (from class `Base`) and a type member `_labelT <;: Label`. 21 | * 22 | * Here is a short example: 23 | * {{{ 24 | * import scala.util.regexp._ 25 | * import scala.util.automata._ 26 | * object MyLang extends WordExp { 27 | * type _regexpT = RegExp 28 | * type _labelT = MyChar 29 | * 30 | * case class MyChar(c:Char) extends Label 31 | * } 32 | * import MyLang._ 33 | * // (a* | b)* 34 | * val rex = Star(Alt(Star(Letter(MyChar('a'))),Letter(MyChar('b')))) 35 | * object MyBerriSethi extends WordBerrySethi { 36 | * override val lang = MyLang 37 | * } 38 | * val nfa = MyBerriSethi.automatonFrom(Sequ(rex), 1) 39 | * }}} 40 | * 41 | * @author Burak Emir 42 | */ 43 | // TODO: still used in ContentModel -- @deprecated("This class will be removed", "2.10.0") 44 | private[dtd] abstract class WordExp extends Base { 45 | 46 | abstract class Label 47 | 48 | override type _regexpT <: RegExp 49 | type _labelT <: Label 50 | 51 | case class Letter(a: _labelT) extends RegExp { 52 | final override lazy val isNullable: Boolean = false 53 | var pos: Int = -1 54 | } 55 | 56 | case class Wildcard() extends RegExp { 57 | final override lazy val isNullable: Boolean = false 58 | var pos: Int = -1 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/ConstructingParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | import java.io.File 18 | import scala.io.Source 19 | 20 | object ConstructingParser { 21 | def fromFile(inp: File, preserveWS: Boolean): ConstructingParser = 22 | new ConstructingParser(Source.fromFile(inp), preserveWS).initialize 23 | 24 | def fromSource(inp: Source, preserveWS: Boolean): ConstructingParser = 25 | new ConstructingParser(inp, preserveWS).initialize 26 | } 27 | 28 | /** 29 | * An xml parser. parses XML and invokes callback methods of a MarkupHandler. 30 | * Don't forget to call next.ch on a freshly instantiated parser in order to 31 | * initialize it. If you get the parser from the object method, initialization 32 | * is already done for you. 33 | * 34 | * {{{ 35 | * object parseFromURL { 36 | * def main(args: Array[String]) { 37 | * val url = args(0) 38 | * val src = scala.io.Source.fromURL(url) 39 | * val cpa = scala.xml.parsing.ConstructingParser.fromSource(src, false) // fromSource initializes automatically 40 | * val doc = cpa.document() 41 | * 42 | * // let's see what it is 43 | * val ppr = new scala.xml.PrettyPrinter(80, 5) 44 | * val ele = doc.docElem 45 | * println("finished parsing") 46 | * val out = ppr.format(ele) 47 | * println(out) 48 | * } 49 | * } 50 | * }}} 51 | */ 52 | class ConstructingParser(override val input: Source, override val preserveWS: Boolean) 53 | extends ConstructingHandler 54 | with ExternalSources 55 | with MarkupParser 56 | -------------------------------------------------------------------------------- /shared/src/main/scala-3/scala/xml/ScalaVersionSpecificReturnTypes.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.xml 14 | 15 | /* 16 | Unlike other Scala-version-specific things, this class is not filling any gaps in capabilities 17 | between different versions of Scala; instead, it mostly documents the types that different versions of the 18 | Scala compiler inferred in the unfortunate absence of the explicit type annotations. 19 | What should have been specified explicitly is given in the comments; 20 | next time we break binary compatibility the types should be changed in the code and this class removed. 21 | */ 22 | private[xml] object ScalaVersionSpecificReturnTypes { // should be 23 | type ExternalIDAttribute = MetaData // Null.type 24 | type NoExternalIDId = String // scala.Null 25 | type NodeNoAttributes = MetaData // Null.type 26 | type NullFilter = MetaData // Null.type 27 | type NullGetNamespace = String // scala.Null 28 | type NullNext = MetaData // scala.Null 29 | type NullKey = String // scala.Null 30 | type NullValue = scala.collection.immutable.Seq[Node] // scala.Null 31 | type NullApply3 = scala.collection.immutable.Seq[Node] // scala.Null 32 | type NullRemove = MetaData // Null.type 33 | type SpecialNodeChild = scala.collection.immutable.Seq[Node] // Nil.type 34 | type GroupChild = scala.collection.immutable.Seq[Node] // Nothing 35 | } 36 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/NodeBuffer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | /** 17 | * This class acts as a Buffer for nodes. If it is used as a sequence of 18 | * nodes `Seq[Node]`, it must be ensured that no updates occur after that 19 | * point, because `scala.xml.Node` is assumed to be immutable. 20 | * 21 | * Despite this being a sequence, don't use it as key in a hashtable. 22 | * Calling the hashcode function will result in a runtime error. 23 | * 24 | * @author Burak Emir 25 | */ 26 | // Note: used by the Scala compiler. 27 | class NodeBuffer extends scala.collection.mutable.ArrayBuffer[Node] with ScalaVersionSpecificNodeBuffer { 28 | /** 29 | * Append given object to this buffer, returns reference on this 30 | * `NodeBuffer` for convenience. Some rules apply: 31 | * - If argument `o` is `'''null'''`, it is ignored. 32 | * - If it is an `Iterator` or `Iterable`, its elements will be added. 33 | * - If `o` is a node, it is added as it is. 34 | * - If it is anything else, it gets wrapped in an [[scala.xml.Atom]]. 35 | * 36 | * @param o converts to an xml node and adds to this node buffer 37 | * @return this nodebuffer 38 | */ 39 | def &+(o: Any): this.type = { 40 | o match { 41 | case null | _: Unit | Text("") => // ignore 42 | case it: Iterator[?] => it.foreach(&+) 43 | case n: Node => super.+=(n) 44 | case ns: Iterable[?] => this &+ ns.iterator 45 | case ns: Array[?] => this &+ ns.iterator 46 | case d => super.+=(new Atom(d)) 47 | } 48 | this 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/transform/BasicTransformer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package transform 16 | 17 | import scala.collection.Seq 18 | 19 | /** 20 | * A class for XML transformations. 21 | * 22 | * @author Burak Emir 23 | */ 24 | abstract class BasicTransformer extends (Node => Node) { 25 | protected def unchanged(n: Node, ns: Seq[Node]): Boolean = 26 | ns.length == 1 && (ns.head == n) 27 | 28 | /** 29 | * Call transform(Node) for each node in ns, append results 30 | * to NodeBuffer. 31 | */ 32 | def transform(it: Iterator[Node], nb: NodeBuffer): Seq[Node] = 33 | it.foldLeft(nb)(_ ++= transform(_)) 34 | 35 | /** 36 | * Call transform(Node) to each node in ns, yield ns if nothing changes, 37 | * otherwise a new sequence of concatenated results. 38 | */ 39 | def transform(ns: Seq[Node]): Seq[Node] = { 40 | val changed: Seq[Node] = ns.flatMap(transform) 41 | if (changed.length != ns.length || changed.zip(ns).exists(p => p._1 != p._2)) changed 42 | else ns 43 | } 44 | 45 | def transform(n: Node): Seq[Node] = 46 | if (n.doTransform) n match { 47 | case Group(xs) => Group(transform(xs)) // un-group the hack Group tag 48 | case _ => 49 | val ch = n.child 50 | val nch = transform(ch) 51 | 52 | if (ch.eq(nch)) n 53 | else Elem(n.prefix, n.label, n.attributes, n.scope, nch.isEmpty, nch.toSeq: _*) 54 | } 55 | else n 56 | 57 | override def apply(n: Node): Node = { 58 | val seq: Seq[Node] = transform(n) 59 | if (seq.length > 1) 60 | throw new UnsupportedOperationException("transform must return single node for root") 61 | else seq.head 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Group.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * A hack to group XML nodes in one node for output. 20 | * 21 | * @author Burak Emir 22 | */ 23 | // Note: used by the Scala compiler. 24 | final case class Group(nodes: Seq[Node]) extends Node { 25 | // Ideally, the `immutable.Seq` would be stored as a field. 26 | // But evolving the case class and remaining binary compatible is very difficult 27 | // Since `Group` is used rarely, call `toSeq` on the field. 28 | // In practice, it should not matter - the `nodes` field anyway contains an `immutable.Seq`. 29 | override def theSeq: ScalaVersionSpecific.SeqOfNode = nodes.toSeq 30 | 31 | override def canEqual(other: Any): Boolean = other match { 32 | case _: Group => true 33 | case _ => false 34 | } 35 | 36 | override def strict_==(other: Equality): Boolean = other match { 37 | case Group(xs) => nodes.sameElements(xs) 38 | case _ => false 39 | } 40 | 41 | override protected def basisForHashCode: Seq[Node] = nodes 42 | 43 | /** 44 | * Since Group is very much a hack it throws an exception if you 45 | * try to do anything with it. 46 | */ 47 | private def fail(msg: String): Nothing = throw new UnsupportedOperationException(s"class Group does not support method '$msg'") 48 | 49 | override def label: Nothing = fail("label") 50 | override def attributes: Nothing = fail("attributes") 51 | override def namespace: Nothing = fail("namespace") 52 | override def child: ScalaVersionSpecificReturnTypes.GroupChild = fail("child") 53 | def buildString(sb: StringBuilder): Nothing = fail("toString(StringBuilder)") 54 | } 55 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/NoBindingFactoryAdapter.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | import scala.collection.Seq 18 | import factory.NodeFactory 19 | 20 | /** 21 | * nobinding adaptor providing callbacks to parser to create elements. 22 | * implements hash-consing 23 | */ 24 | class NoBindingFactoryAdapter extends FactoryAdapter with NodeFactory[Elem] { 25 | /** True. Every XML node may contain text that the application needs */ 26 | override def nodeContainsText(label: String): Boolean = true 27 | 28 | /** From NodeFactory. Constructs an instance of scala.xml.Elem -- TODO: deprecate as in Elem */ 29 | override protected def create(pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, children: Seq[Node]): Elem = 30 | Elem(pre, label, attrs, scope, children.isEmpty, children.toSeq: _*) 31 | 32 | /** From FactoryAdapter. Creates a node. never creates the same node twice, using hash-consing. 33 | TODO: deprecate as in Elem, or forward to create?? */ 34 | override def createNode(pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, children: List[Node]): Elem = 35 | Elem(pre, label, attrs, scope, children.isEmpty, children: _*) 36 | 37 | /** Creates a text node. */ 38 | override def createText(text: String): Text = makeText(text) 39 | 40 | /** Creates a processing instruction. */ 41 | override def createProcInstr(target: String, data: String): Seq[ProcInstr] = makeProcInstr(target, data) 42 | 43 | /** Creates a comment. */ 44 | override def createComment(characters: String): Seq[Comment] = makeComment(characters) 45 | 46 | /** Creates a cdata. */ 47 | override def createPCData(characters: String): PCData = makePCData(characters) 48 | } 49 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/Base.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | /** 17 | * Basic regular expressions. 18 | * 19 | * @author Burak Emir 20 | */ 21 | 22 | @deprecated("This class will be removed", "2.10.0") 23 | private[dtd] abstract class Base { 24 | type _regexpT <: RegExp 25 | 26 | abstract class RegExp { 27 | def isNullable: Boolean 28 | } 29 | 30 | object Alt { 31 | /** `Alt( R,R,R* )`. */ 32 | def apply(rs: _regexpT*): Alt = 33 | if (rs.size < 2) throw new SyntaxError("need at least 2 branches in Alt") 34 | else new Alt(rs: _*) 35 | // Can't enforce that statically without changing the interface 36 | // def apply(r1: _regexpT, r2: _regexpT, rs: _regexpT*) = new Alt(Seq(r1, r2) ++ rs: _*) 37 | def unapplySeq(x: Alt): Some[Seq[_regexpT]] = Some(x.rs) 38 | } 39 | 40 | class Alt private (val rs: _regexpT*) extends RegExp { 41 | final override val isNullable: Boolean = rs.exists(_.isNullable) 42 | } 43 | 44 | object Sequ { 45 | /** Sequ( R,R* ) */ 46 | def apply(rs: _regexpT*): RegExp = if (rs.isEmpty) Eps else new Sequ(rs: _*) 47 | def unapplySeq(x: Sequ): Some[Seq[_regexpT]] = Some(x.rs) 48 | } 49 | 50 | class Sequ private (val rs: _regexpT*) extends RegExp { 51 | final override val isNullable: Boolean = rs.forall(_.isNullable) 52 | } 53 | 54 | case class Star(r: _regexpT) extends RegExp { 55 | final override lazy val isNullable: Boolean = true 56 | } 57 | 58 | // The empty Sequ. 59 | case object Eps extends RegExp { 60 | final override lazy val isNullable: Boolean = true 61 | override def toString: String = "Eps" 62 | } 63 | 64 | /** this class can be used to add meta information to regexps. */ 65 | class Meta(r1: _regexpT) extends RegExp { 66 | final override val isNullable: Boolean = r1.isNullable 67 | def r: _regexpT = r1 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/include/XIncludeException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package include 16 | 17 | /** 18 | * `XIncludeException` is the generic superclass for all checked exceptions 19 | * that may be thrown as a result of a violation of XInclude's rules. 20 | * 21 | * Constructs an `XIncludeException` with the specified detail message. 22 | * The error message string `message` can later be retrieved by the 23 | * `{@link java.lang.Throwable#getMessage}` 24 | * method of class `java.lang.Throwable`. 25 | * 26 | * @param message the detail message. 27 | */ 28 | class XIncludeException(message: String) extends Exception(message) { 29 | 30 | /** 31 | * uses `'''null'''` as its error detail message. 32 | */ 33 | def this() = this(null) 34 | 35 | private var rootCause: Throwable = _ 36 | 37 | /** 38 | * When an `IOException`, `MalformedURLException` or other generic 39 | * exception is thrown while processing an XML document for XIncludes, 40 | * it is customarily replaced by some form of `XIncludeException`. 41 | * This method allows you to store the original exception. 42 | * 43 | * @param nestedException the underlying exception which 44 | * caused the XIncludeException to be thrown 45 | */ 46 | def setRootCause(nestedException: Throwable): Unit = { 47 | this.rootCause = nestedException 48 | } 49 | 50 | /** 51 | * When an `IOException`, `MalformedURLException` or other generic 52 | * exception is thrown while processing an XML document for XIncludes, 53 | * it is customarily replaced by some form of `XIncludeException`. 54 | * This method allows you to retrieve the original exception. 55 | * It returns null if no such exception caused this `XIncludeException`. 56 | * 57 | * @return Throwable the underlying exception which caused the 58 | * `XIncludeException` to be thrown 59 | */ 60 | def getRootCause: Throwable = this.rootCause 61 | } 62 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/PrintEmptyElementsTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import JUnitAssertsForXML.assertEquals 5 | 6 | class PrintEmptyElementsTest { 7 | 8 | @Test 9 | def representEmptyXMLElementsInShortForm(): Unit = { 10 | val expected: String = 11 | """| 12 | | 13 | | 14 | | 15 | | 16 | |is pretty cool 17 | |""".stripMargin 18 | // the xml snippet is not indented because indentation affects pretty printing 19 | // results 20 | val actual: NodeSeq = 21 | 22 | 23 | 24 | 25 | 26 | is pretty cool 27 | 28 | assertEquals(expected, actual) 29 | } 30 | 31 | @Test 32 | def programmaticLong(): Unit = { 33 | assertEquals(" ", 34 | Elem(null, "emptiness", Null, TopScope, minimizeEmpty = false) ++ Text(" ") ++ Comment("programmatic long")) 35 | } 36 | 37 | @Test 38 | def programmaticShort(): Unit = { 39 | assertEquals(" ", 40 | Elem(null, "vide", Null, TopScope, minimizeEmpty = true) ++ Text(" ") ++ Comment("programmatic short")) 41 | } 42 | 43 | @Test 44 | def programmaticShortWithAttribute(): Unit = { 45 | assertEquals(""" """, 46 | Elem(null, "elem", Attribute("attr", Text("value"), Null), TopScope, minimizeEmpty = true) ++ Text(" ") ++ Comment ("programmatic short with attribute")) 47 | } 48 | 49 | @Test 50 | def programmaticLongWithAttribute(): Unit = { 51 | assertEquals(""" """, 52 | Elem(null, "elem2", Attribute("attr2", Text("value2"), Null), TopScope, minimizeEmpty = false) ++ Text(" ") ++ Comment ("programmatic long with attribute")) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/MetaDataTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.assertEquals 5 | 6 | class MetaDataTest { 7 | 8 | @Test 9 | def absentElementPrefixed1(): Unit = { 10 | // type ascription to help overload resolution pick the right variant 11 | assertEquals(null: Object, Null("za://foo.com", TopScope, "bar")) 12 | assertEquals(null, Null("bar")) 13 | } 14 | 15 | @Test 16 | def absentElementPrefixed2(): Unit = { 17 | assertEquals(Option.empty, Null.get("za://foo.com", TopScope, "bar" )) 18 | assertEquals(Option.empty, Null.get("bar")) 19 | } 20 | 21 | @Test 22 | def presentElement1(): Unit = { 23 | val x: PrefixedAttribute = new PrefixedAttribute("zo","bar", new Atom(42), Null) 24 | val s: NamespaceBinding = NamespaceBinding("zo","za://foo.com", TopScope) 25 | assertEquals(new Atom(42), x("za://foo.com", s, "bar" )) 26 | assertEquals(null, x("bar")) 27 | assertEquals(Some(new Atom(42)), x.get("za://foo.com", s, "bar")) 28 | assertEquals(Option.empty, x.get("bar")) 29 | } 30 | 31 | @Test 32 | def presentElement2(): Unit = { 33 | val s: NamespaceBinding = NamespaceBinding("zo","za://foo.com", TopScope) 34 | val x1: PrefixedAttribute = new PrefixedAttribute("zo","bar", new Atom(42), Null) 35 | val x: UnprefixedAttribute = new UnprefixedAttribute("bar","meaning", x1) 36 | assertEquals(null, x(null, s, "bar")) 37 | assertEquals(Text("meaning"), x("bar")) 38 | assertEquals(None, x.get(null, s, "bar" )) 39 | assertEquals(Some(Text("meaning")), x.get("bar")) 40 | } 41 | 42 | @Test 43 | def attributeExtractor(): Unit = { 44 | def domatch(x: Node): Node = { 45 | x match { 46 | case Node("foo", md @ UnprefixedAttribute(_, value, _), _*) if value.nonEmpty => 47 | md("bar")(0) 48 | case _ => new Atom(3) 49 | } 50 | } 51 | val z: Elem = 52 | val z2: Elem = 53 | assertEquals(Text("gar"), domatch(z)) 54 | assertEquals(new Atom(3), domatch(z2)) 55 | } 56 | 57 | @Test 58 | def reverseTest(): Unit = { 59 | assertEquals("", Null.reverse.toString) 60 | assertEquals(""" b="c"""", .attributes.reverse.toString) 61 | assertEquals(""" d="e" b="c"""", .attributes.reverse.toString) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/Inclusion.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * A fast test of language inclusion between minimal automata. 20 | * inspired by the ''AMoRE automata library''. 21 | * 22 | * @author Burak Emir 23 | */ 24 | @deprecated("This class will be removed", "2.10.0") 25 | private[dtd] trait Inclusion[A <: AnyRef] { 26 | 27 | val labels: Seq[A] 28 | 29 | /** 30 | * Returns true if `dfa1` is included in `dfa2`. 31 | */ 32 | def inclusion(dfa1: DetWordAutom[A], dfa2: DetWordAutom[A]): Boolean = { 33 | 34 | def encode(q1: Int, q2: Int): Int = 1 + q1 + q2 * dfa1.nstates 35 | def decode2(c: Int): Int = (c - 1) / dfa1.nstates //integer division 36 | def decode1(c: Int): Int = (c - 1) % dfa1.nstates 37 | 38 | var q1: Int = 0 //dfa1.initstate; // == 0 39 | var q2: Int = 0 //dfa2.initstate; // == 0 40 | 41 | val max: Int = 1 + dfa1.nstates * dfa2.nstates 42 | val mark: Array[Int] = new Array[Int](max) 43 | 44 | var result: Boolean = true 45 | var current: Int = encode(q1, q2) 46 | var last: Int = current 47 | mark(last) = max // mark (q1,q2) 48 | while (current != 0 && result) { 49 | //Console.println("current = [["+q1+" "+q2+"]] = "+current); 50 | for (letter <- labels) { 51 | val r1: Int = dfa1.next(q1, letter) 52 | val r2: Int = dfa2.next(q2, letter) 53 | if (dfa1.isFinal(r1) && !dfa2.isFinal(r2)) 54 | result = false 55 | val test: Int = encode(r1, r2) 56 | //Console.println("test = [["+r1+" "+r2+"]] = "+test); 57 | if (mark(test) == 0) { 58 | mark(last) = test 59 | mark(test) = max 60 | last = test 61 | } 62 | } 63 | val ncurrent: Int = mark(current) 64 | if (ncurrent != max) { 65 | q1 = decode1(ncurrent) 66 | q2 = decode2(ncurrent) 67 | current = ncurrent 68 | } else { 69 | current = 0 70 | } 71 | } 72 | result 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/XMLSyntaxTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import scala.collection.Seq 4 | import org.junit.Test 5 | import org.junit.Assert.assertTrue 6 | import org.junit.Assert.assertFalse 7 | import org.junit.Assert.assertEquals 8 | 9 | class XMLSyntaxTest { 10 | 11 | private def handle[A](x: Node): A = { 12 | x.child(0).asInstanceOf[Atom[A]].data 13 | } 14 | 15 | @Test 16 | def test1(): Unit = { 17 | val xNull: Elem = {null} // these used to be Atom(unit), changed to empty children 18 | assertTrue(xNull.child.sameElements(Nil)) 19 | 20 | val x0: Elem = {} // these used to be Atom(unit), changed to empty children 21 | val x00: Elem = { } // dto. 22 | val xa: Elem = { "world" } 23 | 24 | assertTrue(x0.child.sameElements(Nil)) 25 | assertTrue(x00.child.sameElements(Nil)) 26 | assertEquals("world", handle[String](xa)) 27 | 28 | val xb: Elem = { 1.5 } 29 | assertEquals(1.5, handle[Double](xb), 0.0) 30 | 31 | val xc: Elem = { 5 } 32 | assertEquals(5, handle[Int](xc).toLong) 33 | 34 | val xd: Elem = { true } 35 | assertEquals(true, handle[Boolean](xd)) 36 | 37 | val xe: Elem = { 5:Short } 38 | assertEquals((5:Short).toLong, handle[Short](xe).toLong) 39 | 40 | val xf: Elem = { val x = 27; x } 41 | assertEquals(27, handle[Int](xf).toLong) 42 | 43 | val xg: Elem = { List(1,2,3,4) } 44 | assertEquals("1 2 3 4", xg.toString) 45 | assertFalse(xg.child.map(_.isInstanceOf[Text]).exists(identity)) 46 | 47 | val xh: Elem = { for(x <- List(1,2,3,4) if x % 2 == 0) yield x } 48 | assertEquals("2 4", xh.toString) 49 | assertFalse(xh.child.map(_.isInstanceOf[Text]).exists(identity)) 50 | } 51 | 52 | /** see SVN r13821 (emir): support for , 53 | * so that Options can be used for optional attributes. 54 | */ 55 | @Test 56 | def test2(): Unit = { 57 | val x1: Option[Seq[Node]] = Some(hello) 58 | val n1: Elem = 59 | assertEquals(x1, n1.attribute("key")) 60 | 61 | val x2: Option[Seq[Node]] = None 62 | val n2: Elem = 63 | assertEquals(x2, n2.attribute("key")) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/factory/NodeFactory.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package factory 16 | 17 | import scala.collection.Seq 18 | 19 | trait NodeFactory[A <: Node] { 20 | val ignoreComments: Boolean = false 21 | val ignoreProcInstr: Boolean = false 22 | 23 | /* default behaviour is to use hash-consing */ 24 | val cache: scala.collection.mutable.HashMap[Int, List[A]] = new scala.collection.mutable.HashMap[Int, List[A]] 25 | 26 | protected def create(pre: String, name: String, attrs: MetaData, scope: NamespaceBinding, children: Seq[Node]): A 27 | 28 | protected def construct(hash: Int, old: List[A], pre: String, name: String, attrSeq: MetaData, scope: NamespaceBinding, children: Seq[Node]): A = { 29 | val el: A = create(pre, name, attrSeq, scope, children) 30 | cache.update(hash, el :: old) 31 | el 32 | } 33 | 34 | def eqElements(ch1: Seq[Node], ch2: Seq[Node]): Boolean = 35 | ch1.view.zipAll(ch2.view, null, null).forall { case (x, y) => x.eq(y) } 36 | 37 | def nodeEquals(n: Node, pre: String, name: String, attrSeq: MetaData, scope: NamespaceBinding, children: Seq[Node]): Boolean = 38 | n.prefix == pre && 39 | n.label == name && 40 | n.attributes == attrSeq && 41 | // scope? 42 | eqElements(n.child, children) 43 | 44 | def makeNode(pre: String, name: String, attrSeq: MetaData, scope: NamespaceBinding, children: Seq[Node]): A = { 45 | val hash: Int = Utility.hashCode(pre, name, attrSeq.##, scope.##, children) 46 | def cons(old: List[A]): A = construct(hash, old, pre, name, attrSeq, scope, children) 47 | 48 | cache.get(hash) match { 49 | case Some(list) => // find structurally equal 50 | list.find(nodeEquals(_, pre, name, attrSeq, scope, children)) match { 51 | case Some(x) => x 52 | case _ => cons(list) 53 | } 54 | case None => cons(Nil) 55 | } 56 | } 57 | 58 | def makeText(s: String): Text = Text(s) 59 | def makePCData(s: String): PCData = 60 | PCData(s) 61 | def makeComment(s: String): Seq[Comment] = 62 | if (ignoreComments) Nil else List(Comment(s)) 63 | def makeProcInstr(t: String, s: String): Seq[ProcInstr] = 64 | if (ignoreProcInstr) Nil else List(ProcInstr(t, s)) 65 | } 66 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/PrefixedAttribute.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * prefixed attributes always have a non-null namespace. 20 | * 21 | * @param pre 22 | * @param key 23 | * @param value the attribute value 24 | * @param next1 25 | */ 26 | // Note: used by the Scala compiler. 27 | class PrefixedAttribute( 28 | override val pre: String, 29 | override val key: String, 30 | _value: Seq[Node], 31 | val next1: MetaData 32 | ) 33 | extends Attribute 34 | { 35 | override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value match { 36 | case ns: ScalaVersionSpecific.SeqOfNode => ns 37 | case _ => _value.toVector 38 | } 39 | 40 | override val next: MetaData = if (value != null) next1 else next1.remove(key) 41 | 42 | /** same as this(pre, key, Text(value), next), or no attribute if value is null */ 43 | def this(pre: String, key: String, value: String, next: MetaData) = 44 | this(pre, key, if (value != null) Text(value) else null: NodeSeq, next) 45 | 46 | /** same as this(pre, key, value.get, next), or no attribute if value is None */ 47 | def this(pre: String, key: String, value: Option[Seq[Node]], next: MetaData) = 48 | this(pre, key, value.orNull, next) 49 | 50 | /** 51 | * Returns a copy of this unprefixed attribute with the given 52 | * next field. 53 | */ 54 | override def copy(next: MetaData): PrefixedAttribute = 55 | new PrefixedAttribute(pre, key, value, next) 56 | 57 | override def getNamespace(owner: Node): String = 58 | owner.getNamespace(pre) 59 | 60 | /** forwards the call to next (because caller looks for unprefixed attribute */ 61 | override def apply(key: String): ScalaVersionSpecific.SeqOfNode = next(key) 62 | 63 | /** 64 | * gets attribute value of qualified (prefixed) attribute with given key 65 | */ 66 | override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecific.SeqOfNode = 67 | if (key == this.key && scope.getURI(pre) == namespace) 68 | value 69 | else 70 | next(namespace, scope, key) 71 | } 72 | 73 | object PrefixedAttribute { 74 | def unapply(x: PrefixedAttribute): Some[(String, String, Seq[Node], MetaData)] = Some((x.pre, x.key, x.value, x.next)) 75 | } 76 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/UnprefixedAttribute.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * Unprefixed attributes have the null namespace, and no prefix field 20 | * 21 | * @author Burak Emir 22 | */ 23 | // Note: used by the Scala compiler. 24 | class UnprefixedAttribute( 25 | override val key: String, 26 | _value: Seq[Node], 27 | next1: MetaData 28 | ) 29 | extends Attribute 30 | { 31 | override val value: ScalaVersionSpecific.SeqOfNode = if (_value == null) null else _value match { 32 | case ns: ScalaVersionSpecific.SeqOfNode => ns 33 | case _ => _value.toVector 34 | } 35 | 36 | final override val pre: scala.Null = null 37 | override val next: MetaData = if (value != null) next1 else next1.remove(key) 38 | 39 | /** same as this(key, Text(value), next), or no attribute if value is null */ 40 | def this(key: String, value: String, next: MetaData) = 41 | this(key, if (value != null) Text(value) else null: NodeSeq, next) 42 | 43 | /** same as this(key, value.get, next), or no attribute if value is None */ 44 | def this(key: String, value: Option[Seq[Node]], next: MetaData) = 45 | this(key, value.orNull, next) 46 | 47 | /** returns a copy of this unprefixed attribute with the given next field*/ 48 | override def copy(next: MetaData): UnprefixedAttribute = new UnprefixedAttribute(key, value, next) 49 | 50 | final override def getNamespace(owner: Node): String = null 51 | 52 | /** 53 | * Gets value of unqualified (unprefixed) attribute with given key, null if not found 54 | * 55 | * @param key 56 | * @return value as Seq[Node] if key is found, null otherwise 57 | */ 58 | override def apply(key: String): ScalaVersionSpecific.SeqOfNode = 59 | if (key == this.key) value else next(key) 60 | 61 | /** 62 | * Forwards the call to next (because caller looks for prefixed attribute). 63 | * 64 | * @param namespace 65 | * @param scope 66 | * @param key 67 | * @return .. 68 | */ 69 | override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecific.SeqOfNode = 70 | next(namespace, scope, key) 71 | } 72 | object UnprefixedAttribute { 73 | def unapply(x: UnprefixedAttribute): Some[(String, Seq[Node], MetaData)] = Some((x.key, x.value, x.next)) 74 | } 75 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Null.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Iterator 17 | import scala.collection.Seq 18 | 19 | /** 20 | * Essentially, every method in here is a dummy, returning Zero[T]. 21 | * It provides a backstop for the unusual collection defined by MetaData, 22 | * sort of a linked list of tails. 23 | * 24 | * @author Burak Emir 25 | */ 26 | // Note: used by the Scala compiler. 27 | case object Null extends MetaData { 28 | override def iterator: Iterator[Nothing] = Iterator.empty 29 | override def size: Int = 0 30 | override def append(m: MetaData, scope: NamespaceBinding = TopScope): MetaData = m 31 | override def filter(f: MetaData => Boolean): ScalaVersionSpecificReturnTypes.NullFilter = this 32 | 33 | override def copy(next: MetaData): MetaData = next 34 | override def getNamespace(owner: Node): ScalaVersionSpecificReturnTypes.NullGetNamespace = null 35 | 36 | override def hasNext: Boolean = false 37 | override def next: ScalaVersionSpecificReturnTypes.NullNext = null 38 | override def key: ScalaVersionSpecificReturnTypes.NullKey = null 39 | override def value: ScalaVersionSpecificReturnTypes.NullValue = null 40 | override def isPrefixed: Boolean = false 41 | 42 | override def length: Int = 0 43 | override def length(i: Int): Int = i 44 | 45 | override def strict_==(other: Equality): Boolean = other match { 46 | case x: MetaData => x.length == 0 47 | case _ => false 48 | } 49 | override protected def basisForHashCode: Seq[Any] = Nil 50 | 51 | override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecificReturnTypes.NullApply3 = null 52 | override def apply(key: String): ScalaVersionSpecific.SeqOfNode = 53 | if (Utility.isNameStart(key.head)) null 54 | else throw new IllegalArgumentException(s"not a valid attribute name '$key', so can never match !") 55 | 56 | override protected def toString1(sb: StringBuilder): Unit = () 57 | override protected def toString1: String = "" 58 | 59 | override def toString: String = "" 60 | 61 | override def buildString(sb: StringBuilder): StringBuilder = sb 62 | 63 | override def wellformed(scope: NamespaceBinding): Boolean = true 64 | 65 | override def remove(key: String): ScalaVersionSpecificReturnTypes.NullRemove = this 66 | override def remove(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecificReturnTypes.NullRemove = this 67 | } 68 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/NondetWordAutom.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | import scala.collection.{immutable, mutable} 17 | import scala.collection.Seq 18 | 19 | /** 20 | * A nondeterministic automaton. States are integers, where 21 | * 0 is always the only initial state. Transitions are represented 22 | * in the delta function. Default transitions are transitions that 23 | * are taken when no other transitions can be applied. 24 | * All states are reachable. Accepting states are those for which 25 | * the partial function `finals` is defined. 26 | */ 27 | // TODO: still used in ContentModel -- @deprecated("This class will be removed", "2.10.0") 28 | private[dtd] abstract class NondetWordAutom[T <: AnyRef] { 29 | val nstates: Int 30 | val labels: Seq[T] 31 | val finals: Array[Int] // 0 means not final 32 | val delta: Array[mutable.Map[T, immutable.BitSet]] 33 | val default: Array[immutable.BitSet] 34 | 35 | /** @return true if the state is final */ 36 | final def isFinal(state: Int): Boolean = finals(state) > 0 37 | 38 | /** @return tag of final state */ 39 | final def finalTag(state: Int): Int = finals(state) 40 | 41 | /** @return true if the set of states contains at least one final state */ 42 | final def containsFinal(Q: immutable.BitSet): Boolean = Q.exists(isFinal) 43 | 44 | /** @return true if there are no accepting states */ 45 | final def isEmpty: Boolean = 0.until(nstates).forall(x => !isFinal(x)) 46 | 47 | /** @return a immutable.BitSet with the next states for given state and label */ 48 | def next(q: Int, a: T): immutable.BitSet = delta(q).getOrElse(a, default(q)) 49 | 50 | /** @return a immutable.BitSet with the next states for given state and label */ 51 | def next(Q: immutable.BitSet, a: T): immutable.BitSet = next(Q, next(_, a)) 52 | def nextDefault(Q: immutable.BitSet): immutable.BitSet = next(Q, default) 53 | 54 | private def next(Q: immutable.BitSet, f: Int => immutable.BitSet): immutable.BitSet = 55 | Q.toSet.map(f).foldLeft(immutable.BitSet.empty)(_ ++ _) 56 | 57 | private def finalStates: immutable.Seq[Int] = 0.until(nstates).filter(isFinal) 58 | override def toString: String = { 59 | val finalString: String = Map(finalStates.map(j => j -> finals(j)): _*).toString 60 | val deltaString: String = 0.until(nstates) 61 | .map(i => s" $i->${delta(i)}\n _>${default(i)}\n").mkString 62 | 63 | s"[NondetWordAutom nstates=$nstates finals=$finalString delta=\n$deltaString" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/ShouldCompile.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | // these tests depend on xml, so they ended up here, 4 | // though really they are compiler tests 5 | 6 | import scala.collection._ 7 | import scala.collection.mutable.ArrayBuffer 8 | 9 | // t1761 10 | class Foo { 11 | val elements: Seq[Node] = Nil 12 | val innerTransform: PartialFunction[Elem, String] = { 13 | case Elem(_, l: String, _, _, _@ _*) if elements.exists(_.label == l) => 14 | l 15 | } 16 | } 17 | 18 | // t2281 19 | class t2281A { 20 | def f(x: Boolean): Seq[Node] = if (x)

else
21 | } 22 | 23 | class t2281B { 24 | def splitSentences(text: String): ArrayBuffer[String] = { 25 | val outarr: ArrayBuffer[String] = new ArrayBuffer[String] 26 | var outstr: StringBuffer = new StringBuffer 27 | var prevspace: Boolean = false 28 | val ctext: String = text.replaceAll("\n+", "\n") 29 | ctext.foreach { c => 30 | outstr.append(c) 31 | if (c == '.' || c == '!' || c == '?' || c == '\n' || c == ':' || c == ';' || (prevspace && c == '-')) { 32 | outarr += outstr.toString 33 | outstr = new StringBuffer 34 | } 35 | if (c == '\n') { 36 | outarr += "\n\n" 37 | } 38 | prevspace = c == ' ' 39 | } 40 | if (outstr.length > 0) { 41 | outarr += outstr.toString 42 | } 43 | outarr 44 | } 45 | 46 | def spanForSentence(x: String, picktext: String): Seq[Node] = 47 | if (x == "\n\n") { 48 |

49 | } else { 50 | { x } 51 | } 52 | 53 | def selectableSentences(text: String, picktext: String): ArrayBuffer[Seq[Node]] = { 54 | val sentences: ArrayBuffer[String] = splitSentences(text) 55 | sentences.map(x => spanForSentence(x, picktext)) 56 | } 57 | } 58 | 59 | // SI-5858 60 | object SI_5858 { 61 | new Elem(null, null, Null, TopScope, true, Nil: _*) // was ambiguous 62 | } 63 | 64 | class Floozy { 65 | def fooz(x: Node => String): Unit = () 66 | def foo(m: Node): Unit = fooz { 67 | case Elem(_, _, _, _, n, _*) if n == m => "gaga" 68 | } 69 | } 70 | 71 | object guardedMatch { // SI-3705 72 | // guard caused verifyerror in oldpatmat -- TODO: move this to compiler test suite 73 | def updateNodes(ns: ScalaVersionSpecific.SeqOfNode): ScalaVersionSpecific.SeqOfNode = 74 | for (subnode <- ns) yield subnode match { 75 | case { _ } if true => abc 76 | case Elem(prefix, label, attribs, scope, children @ _*) => 77 | Elem(prefix, label, attribs, scope, minimizeEmpty = true, updateNodes(children): _*) 78 | case other => other 79 | } 80 | updateNodes() 81 | } 82 | 83 | // SI-6897 84 | object shouldCompile { 85 | val html: Seq[Node] = (null: Any) match { 86 | case 1 => 87 | case 2 =>

88 | } 89 | } 90 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/ExternalID.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package dtd 16 | 17 | /** 18 | * an ExternalIDs - either PublicID or SystemID 19 | * 20 | * @author Burak Emir 21 | */ 22 | sealed abstract class ExternalID extends parsing.TokenTests { 23 | def quoted(s: String): String = { 24 | val c: Char = if (s.contains('"')) '\'' else '"' 25 | s"$c$s$c" 26 | } 27 | 28 | // public != null: PUBLIC " " publicLiteral " " [systemLiteral] 29 | // public == null: SYSTEM " " systemLiteral 30 | override def toString: String = 31 | if (publicId == null) s"SYSTEM ${quoted(systemId)}" else 32 | if (systemId == null) s"PUBLIC ${quoted(publicId)}" else 33 | s"PUBLIC ${quoted(publicId)} ${quoted(systemId)}" 34 | 35 | def buildString(sb: StringBuilder): StringBuilder = 36 | sb.append(this.toString) 37 | 38 | def systemId: String 39 | def publicId: String 40 | } 41 | 42 | /** 43 | * a system identifier 44 | * 45 | * @author Burak Emir 46 | * @param systemId the system identifier literal 47 | */ 48 | case class SystemID(override val systemId: String) extends ExternalID { 49 | override val publicId: scala.Null = null 50 | 51 | if (!checkSysID(systemId)) 52 | throw new IllegalArgumentException("can't use both \" and ' in systemId") 53 | } 54 | 55 | /** 56 | * a public identifier (see http://www.w3.org/QA/2002/04/valid-dtd-list.html). 57 | * 58 | * @author Burak Emir 59 | * @param publicId the public identifier literal 60 | * @param systemId (can be null for notation pubIDs) the system identifier literal 61 | */ 62 | case class PublicID(override val publicId: String, override val systemId: String) extends ExternalID { 63 | if (!checkPubID(publicId)) 64 | throw new IllegalArgumentException("publicId must consist of PubidChars") 65 | 66 | if (systemId != null && !checkSysID(systemId)) 67 | throw new IllegalArgumentException("can't use both \" and ' in systemId") 68 | 69 | /** the constant "#PI" */ 70 | def label: String = "#PI" 71 | 72 | /** always empty */ 73 | def attribute: ScalaVersionSpecificReturnTypes.ExternalIDAttribute = Node.NoAttributes 74 | 75 | /** always empty */ 76 | def child: Nil.type = Nil 77 | } 78 | 79 | /** 80 | * A marker used when a `DocType` contains no external id. 81 | * 82 | * @author Michael Bayne 83 | */ 84 | object NoExternalID extends ExternalID { 85 | override val publicId: ScalaVersionSpecificReturnTypes.NoExternalIDId = null 86 | override val systemId: ScalaVersionSpecificReturnTypes.NoExternalIDId = null 87 | 88 | override def toString: String = "" 89 | } 90 | -------------------------------------------------------------------------------- /shared/src/test/scala-2.x/scala/xml/TransformersTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import scala.collection.Seq 4 | import scala.xml.transform._ 5 | 6 | import org.junit.Test 7 | import org.junit.Assert.assertEquals 8 | 9 | class TransformersTest { 10 | 11 | def transformer: RuleTransformer = new RuleTransformer(new RewriteRule { 12 | override def transform(n: Node): NodeSeq = n match { 13 | case { _* } => 14 | case n => n 15 | } 16 | }) 17 | 18 | @Test 19 | def transform(): Unit = // SI-2124 20 | assertEquals(transformer.transform(

), 21 |

) 22 | 23 | @Test 24 | def transformNamespaced(): Unit = // SI-2125 25 | assertEquals(transformer.transform(

), 26 | Group(

)) 27 | 28 | @Test 29 | def rewriteRule(): Unit = { // SI-2276 30 | val inputXml: Node = 31 | 32 | 33 | 1 34 | 35 | 36 | 1 37 | 38 | 39 | 40 | object t1 extends RewriteRule { 41 | override def transform(n: Node): Seq[Node] = n match { 42 | case { x } if x.toString.toInt < 4 => { x.toString.toInt + 1 } 43 | case other => other 44 | } 45 | } 46 | 47 | val ruleTransformer: RuleTransformer = new RuleTransformer(t1) 48 | JUnitAssertsForXML.assertEquals(ruleTransformer(inputXml).toString, // TODO: why do we need toString? 49 | 50 | 51 | 2 52 | 53 | 54 | 2 55 | 56 | ) 57 | } 58 | 59 | @Test 60 | def preserveReferentialComplexityInLinearComplexity(): Unit = { // SI-4528 61 | var i: Int = 0 62 | 63 | val xmlNode: Elem =

Hello Example

64 | 65 | new RuleTransformer(new RewriteRule { 66 | override def transform(n: Node): Seq[Node] = { 67 | n match { 68 | case t: Text if t.text.trim.nonEmpty => 69 | i += 1 70 | Text(s"${t.text}!") 71 | case _ => n 72 | } 73 | } 74 | }).transform(xmlNode) 75 | 76 | assertEquals(1, i) 77 | } 78 | 79 | @Test 80 | def appliesRulesRecursivelyOnPreviousChanges(): Unit = { // #257 81 | def add(outer: Elem, inner: Node): RewriteRule = new RewriteRule { 82 | override def transform(n: Node): Seq[Node] = n match { 83 | case e: Elem if e.label == outer.label => e.copy(child = e.child ++ inner) 84 | case other => other 85 | } 86 | } 87 | 88 | def transformer: RuleTransformer = new RuleTransformer(add(, ), add(, )) 89 | 90 | assertEquals(, transformer()) 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | 15 | /** 16 | * This library provides support for the XML literal syntax in Scala programs. 17 | * {{{ 18 | * val planets: scala.xml.Elem = 19 | * 20 | * Earth 21 | * 5.9742e24 22 | * 6378.14e3 23 | * 24 | * 25 | * Mars 26 | * 0.64191e24 27 | * 3397.0e3 28 | * 29 | * 30 | * }}} 31 | * 32 | * Additionally, you can mix Scala expressions in your XML elements by 33 | * using the curly brace notation: 34 | * 35 | * {{{ 36 | * val sunMass = 1.99e30 37 | * val sunRadius = 6.96e8 38 | * val star = 39 | * Sun 40 | * { sunMass } 41 | * { sunRadius } 42 | * { 4 * Math.PI * Math.pow(sunRadius, 2) } 43 | * { 4/3 * Math.PI * Math.pow(sunRadius, 3) } 44 | * 45 | * }}} 46 | * 47 | * An XML element, for example `` and ``, is 48 | * represented in this library as a case class, [[scala.xml.Elem]]. 49 | * 50 | * The sub-elements of XML values share a common base class, 51 | * [[scala.xml.Node]]. 52 | * 53 | * However, the non-element declarations found in XML files share a 54 | * different common base class, [[scala.xml.dtd.Decl]]. Additionally, 55 | * document type declarations are represented by a different trait, 56 | * [[scala.xml.dtd.DTD]]. 57 | * 58 | * For reading and writing XML data to and from files, see 59 | * [[scala.xml.XML]]. The default parser of XML data is the 60 | * [[http://xerces.apache.org/ Xerces]] parser and is provided in Java 61 | * by [[javax.xml.parsers.SAXParser]]. 62 | * 63 | * For more control of the input, use the parser written in Scala that 64 | * is provided, [[scala.xml.parsing.ConstructingParser]]. 65 | * 66 | * For working with XHTML input, use [[scala.xml.parsing.XhtmlParser]]. 67 | * 68 | * For more control of the output, use the [[scala.xml.PrettyPrinter]]. 69 | * 70 | * Utility methods for working with XML data are provided in 71 | * [[scala.xml.Utility]]. 72 | * 73 | * XML values in Scala are immutable, but you can traverse and 74 | * transform XML data with a [[scala.xml.transform.RuleTransformer]]. 75 | */ 76 | package object xml { 77 | val XercesClassName: String = "org.apache.xerces.parsers.SAXParser" 78 | 79 | type SAXException = org.xml.sax.SAXException 80 | type SAXParseException = org.xml.sax.SAXParseException 81 | type EntityResolver = org.xml.sax.EntityResolver 82 | type InputSource = org.xml.sax.InputSource 83 | type XMLReader = org.xml.sax.XMLReader 84 | type SAXParser = javax.xml.parsers.SAXParser 85 | } 86 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/NamespaceBinding.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * The class `NamespaceBinding` represents namespace bindings 20 | * and scopes. The binding for the default namespace is treated as a null 21 | * prefix. the absent namespace is represented with the null uri. Neither 22 | * prefix nor uri may be empty, which is not checked. 23 | * 24 | * @author Burak Emir 25 | */ 26 | // Note: used by the Scala compiler. 27 | @SerialVersionUID(0 - 2518644165573446725L) 28 | case class NamespaceBinding(prefix: String, uri: String, parent: NamespaceBinding) extends AnyRef with Equality { 29 | if (prefix == "") 30 | throw new IllegalArgumentException("zero length prefix not allowed") 31 | 32 | def getURI(prefix: String): String = 33 | if (this.prefix == prefix) uri else parent.getURI(prefix) 34 | 35 | /** 36 | * Returns some prefix that is mapped to the URI. 37 | * 38 | * @param _uri the input URI 39 | * @return the prefix that is mapped to the input URI, or null 40 | * if no prefix is mapped to the URI. 41 | */ 42 | def getPrefix(uri: String): String = 43 | if (uri == this.uri) prefix else parent.getPrefix(uri) 44 | 45 | override def toString: String = Utility.sbToString(buildString(_, TopScope)) 46 | 47 | private def shadowRedefined(stop: NamespaceBinding): NamespaceBinding = { 48 | def prefixList(x: NamespaceBinding): List[String] = 49 | if ((x == null) || x.eq(stop)) Nil 50 | else x.prefix :: prefixList(x.parent) 51 | def fromPrefixList(l: List[String]): NamespaceBinding = l match { 52 | case Nil => stop 53 | case x :: xs => NamespaceBinding(x, this.getURI(x), fromPrefixList(xs)) 54 | } 55 | val ps0: List[String] = prefixList(this).reverse 56 | val ps: List[String] = ps0.distinct 57 | if (ps.size == ps0.size) this 58 | else fromPrefixList(ps) 59 | } 60 | 61 | override def canEqual(other: Any): Boolean = other match { 62 | case _: NamespaceBinding => true 63 | case _ => false 64 | } 65 | 66 | override def strict_==(other: Equality): Boolean = other match { 67 | case x: NamespaceBinding => (prefix == x.prefix) && (uri == x.uri) && (parent == x.parent) 68 | case _ => false 69 | } 70 | 71 | override def basisForHashCode: Seq[Any] = List(prefix, uri, parent) 72 | 73 | def buildString(stop: NamespaceBinding): String = Utility.sbToString(buildString(_, stop)) 74 | 75 | def buildString(sb: StringBuilder, stop: NamespaceBinding): Unit = 76 | shadowRedefined(stop).doBuildString(sb, stop) 77 | 78 | private def doBuildString(sb: StringBuilder, stop: NamespaceBinding): Unit = { 79 | if (List(null, stop, TopScope).contains(this)) return 80 | 81 | val prefixStr: String = if (prefix != null) s":$prefix" else "" 82 | val uriStr: String = if (uri != null) uri else "" 83 | parent.doBuildString(sb.append(s""" xmlns$prefixStr="$uriStr""""), stop) // copy(ignore) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/NodeSeqTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.Test 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Assert.fail 6 | 7 | import scala.collection.immutable 8 | 9 | class NodeSeqTest { 10 | 11 | @Test 12 | def testAppend(): Unit = { // Bug #392. 13 | val a: NodeSeq = Hello 14 | val b: Elem = Hi 15 | a ++ Hi match { 16 | case res: NodeSeq => assertEquals(2, res.size.toLong) 17 | case _: Seq[Node] => fail("Should be NodeSeq was Seq[Node]") // Unreachable code? 18 | } 19 | val res: NodeSeq = a ++ b 20 | val exp: NodeSeq = NodeSeq.fromSeq(Seq(Hello, Hi)) 21 | assertEquals(exp, res) 22 | } 23 | 24 | @Test 25 | def testAppendedAll(): Unit = { // Bug #392. 26 | val a: NodeSeq = Hello 27 | val b: Elem = Hi 28 | a :+ Hi match { 29 | case res: Seq[Node] => assertEquals(2, res.size.toLong) 30 | case _: NodeSeq => fail("Should be Seq[Node] was NodeSeq") // Unreachable code? 31 | } 32 | val res: NodeSeq = a :+ b // implicit seqToNodeSeq 33 | val exp: NodeSeq = NodeSeq.fromSeq(Seq(Hello, Hi)) 34 | assertEquals(exp, res) 35 | } 36 | 37 | @Test 38 | def testPrepended(): Unit = { 39 | val a: NodeSeq = Hello 40 | val b: Elem = Hi 41 | a +: Hi match { 42 | case res: Seq[Node] => assertEquals(2, res.size.toLong) 43 | case _: NodeSeq => fail("Should be Seq[Node] was NodeSeq") // Unreachable code? 44 | } 45 | val res: Seq[NodeSeq] = a +: b 46 | val exp: NodeBuffer = { 47 | HelloHi 48 | } 49 | assertEquals(exp.toSeq, res) 50 | } 51 | 52 | @Test 53 | def testPrependedAll(): Unit = { 54 | val a: NodeSeq = Hello 55 | val b: Elem = Hi 56 | val c: Elem = Hey 57 | a ++: Hi ++: Hey match { 58 | case res: Seq[Node] => assertEquals(3, res.size.toLong) 59 | case _: NodeSeq => fail("Should be Seq[Node] was NodeSeq") // Unreachable code? 60 | } 61 | val res: NodeSeq = a ++: b ++: c // implicit seqToNodeSeq 62 | val exp: NodeSeq = NodeSeq.fromSeq(Seq(Hello, Hi, Hey)) 63 | assertEquals(exp, res) 64 | } 65 | 66 | @Test 67 | def testMap(): Unit = { 68 | val a: NodeSeq = Hello 69 | val exp: NodeSeq = Seq(Hi) // implicit seqToNodeSeq 70 | assertEquals(exp, a.map(_ => Hi)) 71 | assertEquals(exp, for { _ <- a } yield { Hi }) 72 | } 73 | 74 | @Test 75 | def testFlatMap(): Unit = { 76 | val a: NodeSeq = Hello 77 | val exp: NodeSeq = Seq(Hi) // implicit seqToNodeSeq 78 | assertEquals(exp, a.flatMap(_ => Seq(Hi))) 79 | assertEquals(exp, for { b <- a; _ <- b } yield { Hi }) 80 | assertEquals(exp, for { b <- a; c <- b; _ <- c } yield { Hi }) 81 | } 82 | 83 | @Test 84 | def testStringProjection(): Unit = { 85 | val a: Elem = 86 | 87 | b 88 | 89 | 90 | e 91 | e 92 | 93 | c 94 | 95 | 96 | val res: Seq[String] = for { 97 | b <- a \ "b" 98 | c <- b.child 99 | e <- (c \ "e").headOption 100 | } yield { 101 | e.text.trim 102 | } 103 | assertEquals(Seq("e"), res) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /shared/src/test/scala/scala/xml/PatternMatchingTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import scala.collection.Seq 4 | import org.junit.Test 5 | import org.junit.Assert.assertTrue 6 | import org.junit.Assert.assertEquals 7 | 8 | class PatternMatchingTest { 9 | @Test 10 | def unprefixedAttribute(): Unit = { 11 | val li: List[String] = List("1", "2", "3", "4") 12 | assertTrue(matchSeq(li)) 13 | assertTrue(matchList(li)) 14 | } 15 | 16 | def matchSeq(args: Seq[String]): Boolean = args match { 17 | case Seq(a, b, c, d @ _*) => true 18 | } 19 | 20 | def matchList(args: List[String]): Boolean = 21 | Elem(null, "bla", Null, TopScope, minimizeEmpty = true, args.map { x => Text(x) }: _*) match { 22 | case Elem(_, _, _, _, Text("1"), _*) => true 23 | } 24 | 25 | @Test 26 | def simpleNode(): Unit = 27 | assertTrue( match { 28 | case => true 29 | }) 30 | 31 | @Test 32 | def nameSpaced(): Unit = 33 | assertTrue( match { 34 | case => true 35 | }) 36 | 37 | val cx: Elem = 38 | crazy text world 39 | 40 | 41 | @Test 42 | def nodeContents(): Unit = { 43 | assertTrue(Utility.trim(cx) match { 44 | case n @ crazy text world if (n \ "@foo") xml_== "bar" => true 45 | }) 46 | assertTrue(Utility.trim(cx) match { 47 | case n @ crazy text world if (n \ "@foo") xml_== "bar" => true 48 | }) 49 | assertTrue( match { 50 | case QNode("gaga", "foo", md, child @ _*) => true 51 | }) 52 | 53 | assertTrue( match { 54 | case Node("foo", md, child @ _*) => true 55 | }) 56 | } 57 | 58 | object SafeNodeSeq { 59 | def unapplySeq(any: Any): Option[Seq[Node]] = any match { 60 | case s: Seq[?] => Some(s.flatMap { 61 | case n: Node => n 62 | case _ => NodeSeq.Empty 63 | }) 64 | case _ => None 65 | } 66 | } 67 | 68 | @Test 69 | def nodeSeq(): Unit = { // t0646 70 | val books: Elem = 71 | 72 | Blabla 73 | Blubabla 74 | Baaaaaaalabla 75 | 76 | 77 | assertTrue(NodeSeq.fromSeq(books.child) match { 78 | case t @ Seq(Blabla) => false 79 | case _ => true 80 | }) 81 | 82 | // SI-1059 83 | val m: PartialFunction[Any, Any] = { case SafeNodeSeq(s @ _*) => s } 84 | 85 | assertEquals(m( ++ ), List(, )) 86 | assertTrue(m.isDefinedAt( ++ )) 87 | } 88 | 89 | @Test 90 | def SI_4124(): Unit = { 91 | val body: Node = hi 92 | 93 | assertTrue((body: AnyRef, "foo") match { 94 | case (node: Node, "bar") => false 95 | case (ser: Serializable, "foo") => true 96 | case (_, _) => false 97 | }) 98 | 99 | assertTrue((body, "foo") match { 100 | case (node: Node, "bar") => false 101 | case (ser: Serializable, "foo") => true 102 | case (_, _) => false 103 | }) 104 | 105 | assertTrue((body: AnyRef, "foo") match { 106 | case (node: Node, "foo") => true 107 | case (ser: Serializable, "foo") => false 108 | case (_, _) => false 109 | }) 110 | 111 | assertTrue((body: AnyRef, "foo") match { 112 | case (node: Node, "foo") => true 113 | case (ser: Serializable, "foo") => false 114 | case (_, _) => false 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /shared/src/main/scala-2.13+/scala/xml/ScalaVersionSpecific.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.xml 14 | 15 | import scala.collection.immutable.StrictOptimizedSeqOps 16 | import scala.collection.{View, SeqOps, IterableOnce, immutable, mutable} 17 | import scala.collection.BuildFrom 18 | import scala.collection.mutable.Builder 19 | 20 | private[xml] object ScalaVersionSpecific { 21 | import NodeSeq.Coll 22 | type CBF[-From, -A, +C] = BuildFrom[From, A, C] 23 | object NodeSeqCBF extends BuildFrom[Coll, Node, NodeSeq] { 24 | def newBuilder(from: Coll): Builder[Node, NodeSeq] = NodeSeq.newBuilder 25 | def fromSpecific(from: Coll)(it: IterableOnce[Node]): NodeSeq = (NodeSeq.newBuilder ++= from).result() 26 | } 27 | type SeqOfNode = scala.collection.immutable.Seq[Node] 28 | type SeqOfText = scala.collection.immutable.Seq[Text] 29 | } 30 | 31 | private[xml] trait ScalaVersionSpecificNodeSeq 32 | extends SeqOps[Node, immutable.Seq, NodeSeq] 33 | with StrictOptimizedSeqOps[Node, immutable.Seq, NodeSeq] { self: NodeSeq => 34 | override def fromSpecific(coll: IterableOnce[Node]): NodeSeq = (NodeSeq.newBuilder ++= coll).result() 35 | override def newSpecificBuilder: mutable.Builder[Node, NodeSeq] = NodeSeq.newBuilder 36 | override def empty: NodeSeq = NodeSeq.Empty 37 | def concat(suffix: IterableOnce[Node]): NodeSeq = 38 | fromSpecific(iterator ++ suffix.iterator) 39 | @inline final def ++ (suffix: Seq[Node]): NodeSeq = concat(suffix) 40 | def appended(base: Node): NodeSeq = 41 | fromSpecific(new View.Appended(this, base)) 42 | def appendedAll(suffix: IterableOnce[Node]): NodeSeq = 43 | concat(suffix) 44 | def prepended(base: Node): NodeSeq = 45 | fromSpecific(new View.Prepended(base, this)) 46 | def prependedAll(prefix: IterableOnce[Node]): NodeSeq = 47 | fromSpecific(prefix.iterator ++ iterator) 48 | def map(f: Node => Node): NodeSeq = 49 | fromSpecific(new View.Map(this, f)) 50 | def flatMap(f: Node => IterableOnce[Node]): NodeSeq = 51 | fromSpecific(new View.FlatMap(this, f)) 52 | 53 | def theSeq: scala.collection.Seq[Node] 54 | } 55 | 56 | private[xml] trait ScalaVersionSpecificNodeBuffer { self: NodeBuffer => 57 | override def className: String = "NodeBuffer" 58 | } 59 | 60 | private[xml] trait ScalaVersionSpecificNode { self: Node => 61 | // These methods are overridden in Node with return type `immutable.Seq`. The declarations here result 62 | // in a bridge method in `Node` with result type `collection.Seq` which is needed for binary compatibility. 63 | def child: scala.collection.Seq[Node] 64 | def nonEmptyChildren: scala.collection.Seq[Node] 65 | } 66 | 67 | private[xml] trait ScalaVersionSpecificMetaData { self: MetaData => 68 | def apply(key: String): scala.collection.Seq[Node] 69 | def apply(namespace_uri: String, owner: Node, key: String): scala.collection.Seq[Node] 70 | def apply(namespace_uri: String, scp: NamespaceBinding, k: String): scala.collection.Seq[Node] 71 | 72 | def value: scala.collection.Seq[Node] 73 | } 74 | 75 | private[xml] trait ScalaVersionSpecificTextBuffer { self: TextBuffer => 76 | def toText: scala.collection.Seq[Text] 77 | } 78 | 79 | private[xml] trait ScalaVersionSpecificUtility { self: Utility.type => 80 | def trimProper(x: Node): scala.collection.Seq[Node] 81 | def parseAttributeValue(value: String): scala.collection.Seq[Node] 82 | } 83 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/parsing/ConstructingParserTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | package parsing 3 | 4 | import scala.io.Source 5 | import org.junit.Test 6 | import scala.xml.JUnitAssertsForXML.{ assertEquals => assertXml } 7 | import org.junit.Assert.assertEquals 8 | 9 | class ConstructingParserTest { 10 | 11 | @Test 12 | def t9060(): Unit = { 13 | val a: String = """""" 14 | val source: Source = new Source { 15 | override val iter: Iterator[Char] = a.iterator 16 | override def reportError(pos: Int, msg: String, out: java.io.PrintStream = Console.err): Unit = () 17 | } 18 | val doc: NodeSeq = ConstructingParser.fromSource(source, preserveWS = false).content(TopScope) 19 | assertXml(a, doc) 20 | } 21 | 22 | /* Example of using SYSTEM in DOCTYPE */ 23 | @Test 24 | def docbookTest(): Unit = { 25 | val xml: String = 26 | """| 27 | | 28 | | Book 29 | | 30 | | Chapter 31 | | Text 32 | | 33 | |""".stripMargin 34 | 35 | val expected: Elem = 36 | Book 37 | 38 | Chapter 39 | Text 40 | 41 | 42 | 43 | val source: Source = new Source { 44 | override val iter: Iterator[Char] = xml.iterator 45 | override def reportError(pos: Int, msg: String, out: java.io.PrintStream = Console.err): Unit = () 46 | } 47 | 48 | val doc: Document = ConstructingParser.fromSource(source, preserveWS = true).document() 49 | 50 | assertEquals(expected, doc.theSeq) 51 | } 52 | 53 | /* Unsupported use of lowercase DOCTYPE and SYSTEM */ 54 | @Test(expected = classOf[scala.xml.parsing.FatalError]) 55 | def docbookFail(): Unit = { 56 | val xml: String = 57 | """| 58 | | 59 | |Book 60 | | 61 | |Chapter 62 | |Text 63 | | 64 | |""".stripMargin 65 | 66 | val source: Source = new Source { 67 | override val iter: Iterator[Char] = xml.iterator 68 | override def reportError(pos: Int, msg: String, out: java.io.PrintStream = Console.err): Unit = () 69 | } 70 | 71 | ConstructingParser.fromSource(source, preserveWS = true).content(TopScope) 72 | } 73 | 74 | @Test 75 | def SI6341issue65(): Unit = { 76 | val str: String = """""" 77 | val cpa: ConstructingParser = ConstructingParser.fromSource(Source.fromString(str), preserveWS = true) 78 | val cpadoc: Document = cpa.document() 79 | val ppr: PrettyPrinter = new PrettyPrinter(80,5) 80 | val out: String = ppr.format(cpadoc.docElem) 81 | assertEquals(str, out) 82 | } 83 | 84 | // https://github.com/scala/scala-xml/issues/541 85 | @Test 86 | def issue541(): Unit = { 87 | val xml: String = 88 | """|""".stripMargin 91 | val parser: ConstructingParser = ConstructingParser.fromSource(Source.fromString(xml), preserveWS = true) 92 | parser.document().docElem // shouldn't crash 93 | } 94 | 95 | @Test(expected = classOf[scala.xml.parsing.FatalError]) 96 | def issue656(): Unit = { 97 | // mismatched quotes should not cause an infinite loop 98 | XhtmlParser(Source.fromString(""" true 31 | case _ => false 32 | } 33 | /** 34 | * {{{ 35 | * (#x20 | #x9 | #xD | #xA)+ 36 | * }}} 37 | */ 38 | final def isSpace(cs: Seq[Char]): Boolean = cs.nonEmpty && cs.forall(isSpace) 39 | 40 | /** These are 99% sure to be redundant but refactoring on the safe side. */ 41 | def isAlpha(c: Char): Boolean = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') 42 | def isAlphaDigit(c: Char): Boolean = isAlpha(c) || (c >= '0' && c <= '9') 43 | 44 | /** 45 | * {{{ 46 | * NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | #xB7 47 | * | CombiningChar | Extender 48 | * }}} 49 | * See [4] and [4a] of Appendix B of XML 1.0 specification. 50 | */ 51 | def isNameChar(ch: Char): Boolean = { 52 | import java.lang.Character._ 53 | // The constants represent groups Mc, Me, Mn, Lm, and Nd. 54 | 55 | isNameStart(ch) || (getType(ch).toByte match { 56 | case COMBINING_SPACING_MARK | 57 | ENCLOSING_MARK | NON_SPACING_MARK | 58 | MODIFIER_LETTER | DECIMAL_DIGIT_NUMBER => true 59 | case _ => ".-:·".contains(ch) 60 | }) 61 | } 62 | 63 | /** 64 | * {{{ 65 | * NameStart ::= ( Letter | '_' | ':' ) 66 | * }}} 67 | * where Letter means in one of the Unicode general 68 | * categories `{ Ll, Lu, Lo, Lt, Nl }`. 69 | * 70 | * We do not allow a name to start with `:`. 71 | * See [4] and Appendix B of XML 1.0 specification 72 | */ 73 | def isNameStart(ch: Char): Boolean = { 74 | import java.lang.Character._ 75 | 76 | getType(ch).toByte match { 77 | case LOWERCASE_LETTER | 78 | UPPERCASE_LETTER | OTHER_LETTER | 79 | TITLECASE_LETTER | LETTER_NUMBER => true 80 | case _ => ":_".contains(ch) 81 | } 82 | } 83 | 84 | /** 85 | * {{{ 86 | * Name ::= ( Letter | '_' ) (NameChar)* 87 | * }}} 88 | * See [5] of XML 1.0 specification. 89 | */ 90 | def isName(s: String): Boolean = 91 | s.nonEmpty && isNameStart(s.head) && s.tail.forall(isNameChar) 92 | 93 | def isPubIDChar(ch: Char): Boolean = 94 | isAlphaDigit(ch) || (isSpace(ch) && ch != '\u0009') || 95 | """-\()+,./:=?;!*#@$_%""".contains(ch) 96 | 97 | /** 98 | * Returns `true` if the encoding name is a valid IANA encoding. 99 | * This method does not verify that there is a decoder available 100 | * for this encoding, only that the characters are valid for an 101 | * IANA encoding name. 102 | * 103 | * @param ianaEncoding The IANA encoding name. 104 | */ 105 | def isValidIANAEncoding(ianaEncoding: Seq[Char]): Boolean = { 106 | def charOK(c: Char): Boolean = isAlphaDigit(c) || "._-".contains(c) 107 | 108 | ianaEncoding.nonEmpty && isAlpha(ianaEncoding.head) && 109 | ianaEncoding.tail.forall(charOK) 110 | } 111 | 112 | def checkSysID(s: String): Boolean = List('"', '\'').exists(c => !s.contains(c)) 113 | def checkPubID(s: String): Boolean = s.forall(isPubIDChar) 114 | } 115 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Scala XML Changes 2 | 3 | ## 2.0.1 (2021-07-21) 4 | 5 | Binary compatible with Scala XML 2.0.0. 6 | 7 | Published for Scala 2.12 and 2.13, Scala 3, Scala.js 1.6, and Scala 8 | Native 0.4. 9 | 10 | ### Added 11 | 12 | - No new functionality. 13 | 14 | ### Fixed 15 | 16 | - Fixed runtime error for `MarkupParser` on Scala 3 by changing the 17 | access modifier of internal class, `WithLookAhead` (#542) 18 | 19 | ## 2.0.0 (2021-05-13) 20 | 21 | Not binary compatible with Scala XML 1.3.0. 22 | 23 | Published for Scala 2.12, 2.13, and 3.0, Scala.js 1.5, 24 | and Scala Native 0.4. 25 | 26 | Artifacts are no longer published for Scala 2.11 and Scala.js 0.6. 27 | 28 | A number of deprecated elements have been removed from the library; 29 | see the "[Removed](#Removed)" section below. The library's JAR byte 30 | size is about 15% smaller. 31 | 32 | ### Added 33 | 34 | - Add `scala.xml.transform.NestingTransformer`, to apply a single rule 35 | recursively, to give the original behavior of `RuleTransformer`, see 36 | below. 37 | - The `apiURL` is now published in ivy metadata so that hyperlinks 38 | exist in downstream projects that reference Scala XML in their 39 | Scaladocs. 40 | - Declare version policy of with early-semver in Mima with 41 | sbt-version-policy plugin 42 | 43 | ### Changed 44 | 45 | - Changes to the default parser settings for the JDK SAXParser, see 46 | [Safe parser defaults](https://github.com/scala/scala-xml/wiki/Safer-parser-defaults) 47 | page on the wiki. 48 | - The parser used by the load methods from `scala.xml.XML` and from 49 | `scala.xml.factory.XMLLoader` is now a `ThreadLocal` instance of 50 | SAXParser to reuse the parser instance and avoid repeatedly 51 | allocating one on every file load. 52 | - Improve `scala.xml.transform.RuleTransformer` to apply all rules recursively. 53 | - Reject invalid comment text that ends in a dash (-) in `scala.xml.Comment`. 54 | - Changed use of `scala.collection.mutable.Stack` in `FactoryAdapter` to a 55 | `scala.collection.immutable.List`. These members were affected. 56 | - `attribStack` 57 | - `hStack` 58 | - `tagStack` 59 | - `scopeStack` 60 | - The abstract class `FactoryAdapter`, see above, is used elsewhere 61 | within the library, as well, so the previous changes are also 62 | inherited by: 63 | - `scala.xml.parsing.NoBindingFactoryAdapter` implemented class 64 | - `scala.xml.factory.XMLLoader.adapter` static member 65 | 66 | ### Fixed 67 | 68 | - Attribute order is preserved for XML elements, not reversed. 69 | - Don't escape quotes in `scala.xml.PCData` and `CDATA` as an XML `"` 70 | 71 | ### Removed 72 | 73 | Most of these deletions are of vestigial code that is either unused, 74 | of poor quality or both. Very few users of Scala XML will even notice 75 | the removed parts. Most users will not be affected. 76 | 77 | The deletions represent about 1500 lines of code (sloc). By 78 | comparison Scala XML is 10,000 sloc, so this is about 15% reduction in 79 | sloc. The code that supports XML literals is maintained upstream in 80 | the Scala compiler, not in the Scala XML library. 81 | 82 | - Remove deprecated `scala.xml.pull.XMLEventReader` 83 | - Remove deprecated versions of `scala.xml.Elem` constructors 84 | - Remove deprecated `scala.xml.Elem.xmlToProcess` and 85 | `scala.xml.Elem.processXml` 86 | - Remove deprecated definitions under `scala.xml.persistent` 87 | - `CachedFileStorage` 88 | - `Index` 89 | - `SetStorage` 90 | - Remove `scala.xml.dtd.impl.PointedHedgeExp` 91 | - Remove `scala.xml.dtd.Scanner` 92 | - Remove `scala.xml.dtd.ContentModelParser` 93 | - Remove `scala.xml.dtd.ElementValidator` 94 | - Remove `scala.xml.factory.Binder` 95 | - Remove `scala.xml.parsing.ValidatingMarkupHandler` 96 | - Remove `scala.xml.Properties` 97 | - Remove `scala.xml.factory.LoggedNodeFactory` 98 | - Remove `scala.xml.parsing.MarkupHandler.log` 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | scala-xml 2 | [![latest release for 2.12](https://img.shields.io/maven-central/v/org.scala-lang.modules/scala-xml_2.12.svg?label=scala+2.12)](http://mvnrepository.com/artifact/org.scala-lang.modules/scala-xml_2.12) 3 | [![latest release for 2.13](https://img.shields.io/maven-central/v/org.scala-lang.modules/scala-xml_2.13.svg?label=scala+2.13)](http://mvnrepository.com/artifact/org.scala-lang.modules/scala-xml_2.13) 4 | [![latest release for 3.0](https://img.shields.io/maven-central/v/org.scala-lang.modules/scala-xml_3.svg?label=scala+3)](http://mvnrepository.com/artifact/org.scala-lang.modules/scala-xml_3) 5 | ========= 6 | 7 | The standard Scala XML library. Please file XML issues here, not at https://github.com/scala/bug/issues or http://github.com/scala/scala3/issues. 8 | 9 | The decoupling of scala-xml from the Scala compiler and standard library is possible because the compiler desugars XML literals in Scala source code into a set of method calls. 10 | Alternative implementations of these calls are welcome! 11 | Compiler code that shows the calls needed: 12 | [Scala 2.11](https://github.com/scala/scala/blob/2.11.x/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala), 13 | [Scala 2.12](https://github.com/scala/scala/blob/2.12.x/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala), 14 | [Scala 2.13](https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala), 15 | [Scala 3](https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/parsing/xml/SymbolicXMLBuilder.scala). 16 | 17 | API documentation is available [here](https://javadoc.io/doc/org.scala-lang.modules/scala-xml_2.13/). 18 | 19 | How-to documentation is available in the [wiki](https://github.com/scala/scala-xml/wiki) 20 | 21 | ## Maintenance status 22 | 23 | This library is community-maintained. Maintainers with merge rights include [@aaron_s_hawley](https://github.com/ashawley) and [@dubinsky](https://github.com/dubinsky). 24 | 25 | Contributors are welcome. Please consult the [contributor guide](https://github.com/scala/scala-xml/wiki/Contributor-guide) on the wiki. 26 | 27 | ## Issues 28 | 29 | Some old issues from the Scala issue tracker have been migrated 30 | here, but not all of them. Community assistance identifying and 31 | migrating still-relevant issues is welcome. See [this 32 | page](https://github.com/scala/scala-xml/issues/62) for details. 33 | 34 | ## Related projects 35 | 36 | - [Advxml](https://github.com/geirolz/advxml) - Functional library combining scala-xml with cats-core 37 | - [Binding.scala](https://github.com/ThoughtWorksInc/Binding.scala) - Reactive programming library 38 | - [ezXML](https://github.com/JulienSt/ezXML) - Extensions for traverse, encoding, decoding and mapping XML 39 | - [http4s-scala-xml](https://http4s.github.io/http4s-scala-xml/) - XML literal support in http4s 40 | - [Json4s XML](https://github.com/json4s/json4s) - Conversion to and from JSON 41 | - [monadic-html](https://github.com/OlivierBlanvillain/monadic-html) - DOM-like event-based programming with XHTML 42 | - [phobos](https://github.com/TinkoffCreditSystems/phobos) - Data-binding library based on stream parsing using Aalto XML 43 | - [scalacheck-xml](https://github.com/typelevel/scalacheck-xml) - Provides Scalacheck instances for scala-xml 44 | - [scalaxb](http://scalaxb.org/) - XML data binding, serialization, SOAP and WSDL support 45 | - [ScalaTags](https://github.com/lihaoyi/scalatags) - Alternative syntax for XML literals 46 | - [scala-xml-dotty](https://github.com/felixmulder/scala-xml-dotty) - Macro library for XML literals in Dotty 47 | - [XML SPaC](https://github.com/dylemma/xml-spac) - Streaming event-based parser combinators 48 | - [xs4s](https://github.com/ScalaWilliam/xs4s) - XML streaming for Scala 49 | - [xtract](https://github.com/lucidsoftware/xtract) - A library for deserializing XML 50 | 51 | You might also [search "XML" on Scaladex](https://index.scala-lang.org/search?q=xml). 52 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Xhtml.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import parsing.XhtmlEntities 17 | import Utility.{ sbToString, isAtomAndNotText } 18 | import scala.collection.Seq 19 | 20 | /* (c) David Pollak 2007 WorldWide Conferencing, LLC */ 21 | 22 | object Xhtml { 23 | /** 24 | * Convenience function: same as toXhtml(node, false, false) 25 | * 26 | * @param node the node 27 | */ 28 | def toXhtml(node: Node): String = sbToString(sb => toXhtml(x = node, sb = sb)) 29 | 30 | /** 31 | * Convenience function: amounts to calling toXhtml(node) on each 32 | * node in the sequence. 33 | * 34 | * @param nodeSeq the node sequence 35 | */ 36 | def toXhtml(nodeSeq: NodeSeq): String = sbToString(sb => sequenceToXML(nodeSeq: Seq[Node], sb = sb)) 37 | 38 | /** 39 | * Elements which we believe are safe to minimize if minimizeTags is true. 40 | * See http://www.w3.org/TR/xhtml1/guidelines.html#C_3 41 | */ 42 | private val minimizableElements: List[String] = 43 | List("base", "meta", "link", "hr", "br", "param", "img", "area", "input", "col") 44 | 45 | def toXhtml( 46 | x: Node, 47 | pscope: NamespaceBinding = TopScope, 48 | sb: StringBuilder = new StringBuilder, 49 | stripComments: Boolean = false, 50 | decodeEntities: Boolean = false, 51 | preserveWhitespace: Boolean = false, 52 | minimizeTags: Boolean = true 53 | ): Unit = { 54 | def decode(er: EntityRef): StringBuilder = XhtmlEntities.entMap.get(er.entityName) match { 55 | case Some(chr) if chr.toInt >= 128 => sb.append(chr) 56 | case _ => er.buildString(sb) 57 | } 58 | def shortForm: Boolean = 59 | minimizeTags && 60 | (x.child == null || x.child.isEmpty) && 61 | minimizableElements.contains(x.label) 62 | 63 | x match { 64 | case c: Comment => if (!stripComments) c.buildString(sb) 65 | case er: EntityRef if decodeEntities => decode(er) 66 | case x: SpecialNode => x.buildString(sb) 67 | case g: Group => 68 | g.nodes.foreach { toXhtml(_, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) } 69 | 70 | case _ => 71 | sb.append('<') 72 | x.nameToString(sb) 73 | if (x.attributes != null) x.attributes.buildString(sb) 74 | x.scope.buildString(sb, pscope) 75 | 76 | if (shortForm) sb.append(" />") 77 | else { 78 | sb.append('>') 79 | sequenceToXML(x.child, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) 80 | sb.append("') 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * Amounts to calling toXhtml(node, ...) with the given parameters on each node. 89 | */ 90 | def sequenceToXML( 91 | children: Seq[Node], 92 | pscope: NamespaceBinding = TopScope, 93 | sb: StringBuilder = new StringBuilder, 94 | stripComments: Boolean = false, 95 | decodeEntities: Boolean = false, 96 | preserveWhitespace: Boolean = false, 97 | minimizeTags: Boolean = true 98 | ): Unit = if (children.nonEmpty) { 99 | val doSpaces: Boolean = children.forall(isAtomAndNotText) // interleave spaces 100 | for (c <- children.take(children.length - 1)) { 101 | toXhtml(c, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) 102 | if (doSpaces) sb.append(' ') 103 | } 104 | toXhtml(children.last, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/BaseBerrySethi.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | import scala.collection.{ mutable, immutable } 17 | import scala.collection.Seq 18 | 19 | // todo: replace global variable pos with acc 20 | 21 | /** 22 | * This class turns a regular expression over `A` into a 23 | * [[scala.xml.dtd.impl.NondetWordAutom]] over `A` using the celebrated 24 | * position automata construction (also called ''Berry-Sethi'' or ''Glushkov''). 25 | */ 26 | @deprecated("This class will be removed", "2.10.0") 27 | private[dtd] abstract class BaseBerrySethi { 28 | val lang: Base 29 | import lang.{ Alt, Eps, Meta, RegExp, Sequ, Star } 30 | 31 | protected var pos: Int = 0 32 | 33 | // results which hold all info for the NondetWordAutomaton 34 | protected var follow: mutable.HashMap[Int, Set[Int]] = _ 35 | 36 | protected var finalTag: Int = _ 37 | 38 | protected var finals: immutable.Map[Int, Int] = _ // final states 39 | 40 | // constants -------------------------- 41 | 42 | final val emptySet: Set[Int] = Set() 43 | 44 | private def doComp(r: RegExp, compFunction: RegExp => Set[Int]): Set[Int] = r match { 45 | case x: Alt => x.rs.map(compFirst).foldLeft(emptySet)(_ ++ _) 46 | case Eps => emptySet 47 | case x: Meta => compFunction(x.r) 48 | case x: Sequ => 49 | val (l1: Seq[lang._regexpT], l2: Seq[lang._regexpT]) = x.rs.span(_.isNullable) 50 | (l1 ++ l2.take(1)).map(compFunction).foldLeft(emptySet)(_ ++ _) 51 | case Star(t) => compFunction(t) 52 | case _ => throw new IllegalArgumentException(s"unexpected pattern ${r.getClass}") 53 | } 54 | 55 | /** Computes `first(r)` for the word regexp `r`. */ 56 | protected def compFirst(r: RegExp): Set[Int] = doComp(r, compFirst) 57 | 58 | /** Computes `last(r)` for the regexp `r`. */ 59 | protected def compLast(r: RegExp): Set[Int] = doComp(r, compLast) 60 | 61 | /** 62 | * Starts from the right-to-left 63 | * precondition: pos is final 64 | * pats are successor patterns of a Sequence node 65 | */ 66 | protected def compFollow(rs: Seq[RegExp]): Set[Int] = { 67 | follow(0) = 68 | if (rs.isEmpty) emptySet 69 | else rs.foldRight(Set(pos))((p, fol) => { 70 | val first: Set[Int] = compFollow1(fol, p) 71 | 72 | if (p.isNullable) fol ++ first 73 | else first 74 | }) 75 | 76 | follow(0) 77 | } 78 | 79 | /** 80 | * Returns the first set of an expression, setting the follow set along the way. 81 | */ 82 | protected def compFollow1(fol1: Set[Int], r: RegExp): Set[Int] = r match { 83 | case x: Alt => Set((x.rs reverseMap (compFollow1(fol1, _))).flatten: _*) 84 | case x: Meta => compFollow1(fol1, x.r) 85 | case x: Star => compFollow1(fol1 ++ compFirst(x.r), x.r) 86 | case x: Sequ => 87 | x.rs.foldRight(fol1) { (p, fol) => 88 | val first: Set[Int] = compFollow1(fol, p) 89 | 90 | if (p.isNullable) fol ++ first 91 | else first 92 | } 93 | case _ => throw new IllegalArgumentException(s"unexpected pattern: ${r.getClass}") 94 | } 95 | 96 | /** 97 | * Returns the "Sethi-length" of a pattern, creating the set of position along the way. 98 | */ 99 | protected def traverse(r: RegExp): Unit = r match { 100 | // (is tree automaton stuff, more than Berry-Sethi) 101 | case x: Alt => x.rs.foreach(traverse) 102 | case x: Sequ => x.rs.foreach(traverse) 103 | case x: Meta => traverse(x.r) 104 | case Star(t) => traverse(t) 105 | case _ => throw new IllegalArgumentException(s"unexp pattern ${r.getClass}") 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/include/sax/EncodingHeuristics.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package include.sax 16 | 17 | import java.io.InputStream 18 | import scala.util.matching.Regex 19 | 20 | /** 21 | * `EncodingHeuristics` reads from a stream 22 | * (which should be buffered) and attempts to guess 23 | * what the encoding of the text in the stream is. 24 | * If it fails to determine the type of the encoding, 25 | * it returns the default UTF-8. 26 | * 27 | * @author Burak Emir 28 | * @author Paul Phillips 29 | */ 30 | object EncodingHeuristics { 31 | object EncodingNames { 32 | // UCS-4 isn't yet implemented in java releases anyway... 33 | val bigUCS4: String = "UCS-4" 34 | val littleUCS4: String = "UCS-4" 35 | val unusualUCS4: String = "UCS-4" 36 | val bigUTF16: String = "UTF-16BE" 37 | val littleUTF16: String = "UTF-16LE" 38 | val utf8: String = "UTF-8" 39 | val default: String = utf8 40 | } 41 | import EncodingNames._ 42 | 43 | /** 44 | * This utility method attempts to determine the XML character encoding 45 | * by examining the input stream, as specified at 46 | * [[http://www.w3.org/TR/xml/#sec-guessing w3]]. 47 | * 48 | * @param in `InputStream` to read from. 49 | * @throws java.io.IOException if the stream cannot be reset 50 | * @return the name of the encoding. 51 | */ 52 | def readEncodingFromStream(in: InputStream): String = { 53 | var ret: String = null 54 | val bytesToRead: Int = 1024 // enough to read most XML encoding declarations 55 | def resetAndRet: String = { in.reset(); ret } 56 | 57 | // This may fail if there are a lot of space characters before the end 58 | // of the encoding declaration 59 | in mark bytesToRead 60 | val bytes: (Int, Int, Int, Int) = (in.read, in.read, in.read, in.read) 61 | 62 | // first look for byte order mark 63 | ret = bytes match { 64 | case (0x00, 0x00, 0xFE, 0xFF) => bigUCS4 65 | case (0xFF, 0xFE, 0x00, 0x00) => littleUCS4 66 | case (0x00, 0x00, 0xFF, 0xFE) => unusualUCS4 67 | case (0xFE, 0xFF, 0x00, 0x00) => unusualUCS4 68 | case (0xFE, 0xFF, _, _) => bigUTF16 69 | case (0xFF, 0xFE, _, _) => littleUTF16 70 | case (0xEF, 0xBB, 0xBF, _) => utf8 71 | case _ => null 72 | } 73 | if (ret != null) 74 | return resetAndRet 75 | 76 | def readASCIIEncoding: String = { 77 | val data: Array[Byte] = new Array[Byte](bytesToRead - 4) 78 | val length: Int = in.read(data, 0, bytesToRead - 4) 79 | 80 | // Use Latin-1 (ISO-8859-1) because all byte sequences are legal. 81 | val declaration: String = new String(data, 0, length, "ISO-8859-1") 82 | val regexp: Regex = """(?m).*?encoding\s*=\s*["'](.+?)['"]""".r 83 | regexp.findFirstMatchIn(declaration) match { 84 | case None => default 85 | case Some(md) => md.subgroups(0) 86 | } 87 | } 88 | 89 | // no byte order mark present; first character must be '<' or whitespace 90 | ret = bytes match { 91 | case (0x00, 0x00, 0x00, '<') => bigUCS4 92 | case ('<', 0x00, 0x00, 0x00) => littleUCS4 93 | case (0x00, 0x00, '<', 0x00) => unusualUCS4 94 | case (0x00, '<', 0x00, 0x00) => unusualUCS4 95 | case (0x00, '<', 0x00, '?') => bigUTF16 // XXX must read encoding 96 | case ('<', 0x00, '?', 0x00) => littleUTF16 // XXX must read encoding 97 | case ('<', '?', 'x', 'm') => readASCIIEncoding 98 | case (0x4C, 0x6F, 0xA7, 0x94) => utf8 // XXX EBCDIC 99 | case _ => utf8 // no XML or text declaration present 100 | } 101 | resetAndRet 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Document.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * A document information item (according to InfoSet spec). The comments 20 | * are copied from the Infoset spec, only augmented with some information 21 | * on the Scala types for definitions that might have no value. 22 | * Also plays the role of an `XMLEvent` for pull parsing. 23 | * 24 | * @author Burak Emir 25 | */ 26 | @SerialVersionUID(-2289320563321795109L) 27 | class Document extends NodeSeq with Serializable { 28 | 29 | /** 30 | * An ordered list of child information items, in document 31 | * order. The list contains exactly one element information item. The 32 | * list also contains one processing instruction information item for 33 | * each processing instruction outside the document element, and one 34 | * comment information item for each comment outside the document 35 | * element. Processing instructions and comments within the DTD are 36 | * excluded. If there is a document type declaration, the list also 37 | * contains a document type declaration information item. 38 | */ 39 | var children: Seq[Node] = _ // effectively an `immutable.Seq`, not changed due to binary compatibility 40 | 41 | /** The element information item corresponding to the document element. */ 42 | var docElem: Node = _ 43 | 44 | /** The dtd that comes with the document, if any */ 45 | var dtd: scala.xml.dtd.DTD = _ 46 | 47 | /** 48 | * An unordered set of notation information items, one for each notation 49 | * declared in the DTD. If any notation is multiply declared, this property 50 | * has no value. 51 | */ 52 | def notations: Seq[scala.xml.dtd.NotationDecl] = 53 | dtd.notations 54 | 55 | /** 56 | * An unordered set of unparsed entity information items, one for each 57 | * unparsed entity declared in the DTD. 58 | */ 59 | def unparsedEntities: Seq[scala.xml.dtd.EntityDecl] = 60 | dtd.unparsedEntities 61 | 62 | /** The base URI of the document entity. */ 63 | var baseURI: String = _ 64 | 65 | /** 66 | * The name of the character encoding scheme in which the document entity 67 | * is expressed. 68 | */ 69 | var encoding: Option[String] = _ 70 | 71 | /** 72 | * An indication of the standalone status of the document, either 73 | * true or false. This property is derived from the optional standalone 74 | * document declaration in the XML declaration at the beginning of the 75 | * document entity, and has no value (`None`) if there is no 76 | * standalone document declaration. 77 | */ 78 | var standAlone: Option[Boolean] = _ 79 | 80 | /** 81 | * A string representing the XML version of the document. This 82 | * property is derived from the XML declaration optionally present at 83 | * the beginning of the document entity, and has no value (`None`) 84 | * if there is no XML declaration. 85 | */ 86 | var version: Option[String] = _ 87 | 88 | /** 89 | * 9. This property is not strictly speaking part of the infoset of 90 | * the document. Rather it is an indication of whether the processor 91 | * has read the complete DTD. Its value is a boolean. If it is false, 92 | * then certain properties (indicated in their descriptions below) may 93 | * be unknown. If it is true, those properties are never unknown. 94 | */ 95 | var allDeclarationsProcessed: Boolean = false 96 | 97 | // methods for NodeSeq 98 | 99 | override def theSeq: ScalaVersionSpecific.SeqOfNode = this.docElem 100 | 101 | override def canEqual(other: Any): Boolean = other match { 102 | case _: Document => true 103 | case _ => false 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Attribute.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * This singleton object contains the `apply` and `unapply` methods for 20 | * convenient construction and deconstruction. 21 | * 22 | * @author Burak Emir 23 | */ 24 | object Attribute { 25 | def unapply(x: Attribute): Option[(String, Seq[Node], MetaData)] = x match { 26 | case PrefixedAttribute(_, key, value, next) => Some((key, value, next)) 27 | case UnprefixedAttribute(key, value, next) => Some((key, value, next)) 28 | case _ => None 29 | } 30 | 31 | /** Convenience functions which choose Un/Prefixedness appropriately */ 32 | def apply(key: String, value: Seq[Node], next: MetaData): Attribute = 33 | new UnprefixedAttribute(key, value, next) 34 | 35 | def apply(pre: String, key: String, value: String, next: MetaData): Attribute = 36 | if (pre == null || pre == "") new UnprefixedAttribute(key, value, next) 37 | else new PrefixedAttribute(pre, key, value, next) 38 | 39 | def apply(pre: String, key: String, value: Seq[Node], next: MetaData): Attribute = 40 | if (pre == null || pre == "") new UnprefixedAttribute(key, value, next) 41 | else new PrefixedAttribute(pre, key, value, next) 42 | 43 | def apply(pre: Option[String], key: String, value: Seq[Node], next: MetaData): Attribute = 44 | pre match { 45 | case None => new UnprefixedAttribute(key, value, next) 46 | case Some(p) => new PrefixedAttribute(p, key, value, next) 47 | } 48 | } 49 | 50 | /** 51 | * The `Attribute` trait defines the interface shared by both 52 | * [[scala.xml.PrefixedAttribute]] and [[scala.xml.UnprefixedAttribute]]. 53 | * 54 | * @author Burak Emir 55 | */ 56 | trait Attribute extends MetaData with ScalaVersionSpecificMetaData { 57 | def pre: String // will be null if unprefixed 58 | override val key: String 59 | override val value: ScalaVersionSpecific.SeqOfNode 60 | override val next: MetaData 61 | 62 | override def apply(key: String): ScalaVersionSpecific.SeqOfNode 63 | override def apply(namespace: String, scope: NamespaceBinding, key: String): ScalaVersionSpecific.SeqOfNode 64 | override def copy(next: MetaData): Attribute 65 | 66 | override def remove(key: String): MetaData = 67 | if (!isPrefixed && this.key == key) next 68 | else copy(next.remove(key)) 69 | 70 | override def remove(namespace: String, scope: NamespaceBinding, key: String): MetaData = 71 | if (this.key == key && scope.getURI(pre) == namespace) next 72 | else copy(next.remove(namespace, scope, key)) 73 | 74 | override def isPrefixed: Boolean = pre != null 75 | 76 | override def getNamespace(owner: Node): String 77 | 78 | override def wellformed(scope: NamespaceBinding): Boolean = { 79 | val arg: String = if (isPrefixed) scope.getURI(pre) else null 80 | (next(arg, scope, key) == null) && next.wellformed(scope) 81 | } 82 | 83 | /** Returns an iterator on attributes */ 84 | override def iterator: Iterator[MetaData] = 85 | if (value == null) next.iterator 86 | else Iterator.single(this) ++ next.iterator 87 | 88 | override def size: Int = 89 | if (value == null) next.size 90 | else 1 + next.size 91 | 92 | /** 93 | * Appends string representation of only this attribute to stringbuffer. 94 | */ 95 | override protected def toString1(sb: StringBuilder): Unit = { 96 | if (value == null) 97 | return 98 | if (isPrefixed) 99 | sb.append(s"$pre:") 100 | 101 | sb.append(s"$key=") 102 | val sb2: StringBuilder = new StringBuilder() 103 | Utility.sequenceToXML(value, TopScope, sb2, stripComments = true) 104 | Utility.appendQuoted(sb2.toString, sb) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /jvm/src/test/scala/scala/xml/ReuseNodesTest.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import scala.xml.transform._ 4 | import scala.collection.Seq 5 | import org.junit.Assert.assertSame 6 | import org.junit.experimental.theories.Theories 7 | import org.junit.experimental.theories.Theory 8 | import org.junit.experimental.theories.DataPoints 9 | import org.junit.runner.RunWith 10 | /** 11 | * This test verifies that after the transform, the resultant xml node 12 | * uses as many old nodes as possible. 13 | * 14 | * Three transformers class for case - 15 | * One for original, one for modified, and one proposed which shows 16 | * all are equivalent when it comes to reusing as many nodes as possible 17 | */ 18 | object ReuseNodesTest { 19 | 20 | class OriginalTranformr(rules: RewriteRule*) extends RuleTransformer(rules:_*) { 21 | override def transform(ns: Seq[Node]): Seq[Node] = { 22 | val xs: Seq[Seq[Node]] = ns.toStream.map(transform) 23 | val (xs1: Seq[(Seq[Node], Node)], xs2: Seq[(Seq[Node], Node)]) = xs.zip(ns).span { case (x, n) => unchanged(n, x) } 24 | 25 | if (xs2.isEmpty) ns 26 | else xs1.map(_._2) ++ xs2.head._1 ++ transform(ns.drop(xs1.length + 1)) 27 | } 28 | override def transform(n: Node): Seq[Node] = super.transform(n) 29 | } 30 | 31 | class ModifiedTranformr(rules: RewriteRule*) extends RuleTransformer(rules:_*) { 32 | override def transform(ns: Seq[Node]): Seq[Node] = { 33 | val changed: Seq[Node] = ns.flatMap(transform) 34 | 35 | if (changed.length != ns.length || changed.zip(ns).exists(p => p._1 != p._2)) changed 36 | else ns 37 | } 38 | override def transform(n: Node): Seq[Node] = super.transform(n) 39 | } 40 | 41 | class AlternateTranformr(rules: RewriteRule*) extends RuleTransformer(rules:_*) { 42 | override def transform(ns: Seq[Node]): Seq[Node] = { 43 | val xs: Seq[Seq[Node]] = ns.toStream.map(transform) 44 | val (xs1: Seq[(Seq[Node], Node)], xs2: Seq[(Seq[Node], Node)]) = xs.zip(ns).span { case (x, n) => unchanged(n, x) } 45 | 46 | if (xs2.isEmpty) ns 47 | else xs1.map(_._2) ++ xs2.head._1 ++ transform(ns.drop(xs1.length + 1)) 48 | } 49 | override def transform(n: Node): Seq[Node] = super.transform(n) 50 | } 51 | 52 | def rewriteRule: RewriteRule = new RewriteRule { 53 | override def transform(n: Node): NodeSeq = n match { 54 | case n if n.label == "change" => Elem( 55 | n.prefix, "changed", n.attributes, n.scope, n.child.isEmpty, n.child : _*) 56 | case _ => n 57 | } 58 | } 59 | 60 | @DataPoints 61 | def tranformers(): Array[RuleTransformer] = Array( 62 | new OriginalTranformr(rewriteRule), 63 | new ModifiedTranformr(rewriteRule), 64 | new AlternateTranformr(rewriteRule)) 65 | } 66 | 67 | @RunWith(classOf[Theories]) 68 | class ReuseNodesTest { 69 | 70 | @Theory 71 | def transformReferentialEquality(rt: RuleTransformer): Unit = { 72 | val original: Elem =

73 | val tranformed: Seq[Node] = rt.transform(original) 74 | assertSame(original, tranformed) 75 | } 76 | 77 | @Theory 78 | def transformReferentialEqualityOnly(rt: RuleTransformer): Unit = { 79 | val original: Elem =
80 | val transformed: Seq[Node] = rt.transform(original) 81 | recursiveAssert(original,transformed) 82 | } 83 | 84 | def recursiveAssert(original: Seq[Node], transformed: Seq[Node]): Unit = { 85 | original.zip(transformed).foreach { 86 | case (x, y) => recursiveAssert(x, y) 87 | } 88 | } 89 | 90 | def recursiveAssert(original: Node, transformed: Node): Unit = { 91 | transformed.label match { 92 | case "changed" => // do nothing expect this node to be changed 93 | recursiveAssert(original.child,transformed.child) 94 | case _ => 95 | assertSame(original, transformed) 96 | // No need to check for children, node being immutable 97 | // children can't be different if parents are referentially equal 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/impl/SubsetConstruction.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml.dtd.impl 15 | 16 | import scala.collection.{immutable, mutable} 17 | 18 | // TODO: still used in ContentModel -- @deprecated("This class will be removed", "2.10.0") 19 | private[dtd] class SubsetConstruction[T <: AnyRef](val nfa: NondetWordAutom[T]) { 20 | import nfa.labels 21 | 22 | def selectTag(Q: immutable.BitSet, finals: Array[Int]): Int = 23 | Q.map(finals).filter(_ > 0).min 24 | 25 | def determinize: DetWordAutom[T] = { 26 | // for assigning numbers to bitsets 27 | val indexMap: mutable.Map[immutable.BitSet, Int] = mutable.Map[immutable.BitSet, Int]() 28 | val invIndexMap: mutable.Map[Int, immutable.BitSet] = mutable.Map[Int, immutable.BitSet]() 29 | var ix: Int = 0 30 | 31 | // we compute the dfa with states = bitsets 32 | val q0: immutable.BitSet = immutable.BitSet(0) // the set { 0 } 33 | val sink: immutable.BitSet = immutable.BitSet.empty // the set { } 34 | 35 | var states: Set[immutable.BitSet] = Set(q0, sink) // initial set of sets 36 | val delta: mutable.HashMap[immutable.BitSet, mutable.HashMap[T, immutable.BitSet]] = 37 | new mutable.HashMap[immutable.BitSet, mutable.HashMap[T, immutable.BitSet]] 38 | val deftrans: mutable.Map[immutable.BitSet, immutable.BitSet] = mutable.Map(q0 -> sink, sink -> sink) // initial transitions 39 | val finals: mutable.Map[immutable.BitSet, Int] = mutable.Map() 40 | var rest: immutable.List[immutable.BitSet] = immutable.List.empty[immutable.BitSet] 41 | 42 | rest = q0 :: sink :: rest 43 | 44 | def addFinal(q: immutable.BitSet): Unit = { 45 | if (nfa.containsFinal(q)) 46 | finals(q) = selectTag(q, nfa.finals) 47 | } 48 | def add(Q: immutable.BitSet): Unit = { 49 | if (!states(Q)) { 50 | states += Q 51 | rest = Q :: rest 52 | addFinal(Q) 53 | } 54 | } 55 | 56 | addFinal(q0) // initial state may also be a final state 57 | 58 | while (rest.nonEmpty) { 59 | val P: immutable.BitSet = rest.head 60 | rest = rest.tail 61 | // assign a number to this bitset 62 | indexMap(P) = ix 63 | invIndexMap(ix) = P 64 | ix += 1 65 | 66 | // make transition map 67 | val Pdelta: mutable.HashMap[T, immutable.BitSet] = new mutable.HashMap[T, immutable.BitSet] 68 | delta.update(P, Pdelta) 69 | 70 | labels.foreach { label => 71 | val Q: immutable.BitSet = nfa.next(P, label) 72 | Pdelta.update(label, Q) 73 | add(Q) 74 | } 75 | 76 | // collect default transitions 77 | val Pdef: immutable.BitSet = nfa nextDefault P 78 | deftrans(P) = Pdef 79 | add(Pdef) 80 | } 81 | 82 | // create DetWordAutom, using indices instead of sets 83 | val nstatesR: Int = states.size 84 | val deltaR: Array[mutable.Map[T, Int]] = new Array[mutable.Map[T, Int]](nstatesR) 85 | val defaultR: Array[Int] = new Array[Int](nstatesR) 86 | val finalsR: Array[Int] = new Array[Int](nstatesR) 87 | 88 | for (Q <- states) { 89 | val q: Int = indexMap(Q) 90 | val trans: mutable.Map[T, immutable.BitSet] = delta(Q) 91 | val transDef: immutable.BitSet = deftrans(Q) 92 | val qDef: Int = indexMap(transDef) 93 | val ntrans: mutable.Map[T, Int] = new mutable.HashMap[T, Int]() 94 | 95 | for ((label, value) <- trans) { 96 | val p: Int = indexMap(value) 97 | if (p != qDef) 98 | ntrans.update(label, p) 99 | } 100 | 101 | deltaR(q) = ntrans 102 | defaultR(q) = qDef 103 | } 104 | 105 | finals.foreach { case (k, v) => finalsR(indexMap(k)) = v } 106 | 107 | new DetWordAutom[T] { 108 | override val nstates: Int = nstatesR 109 | override val delta: Array[mutable.Map[T, Int]] = deltaR 110 | override val default: Array[Int] = defaultR 111 | override val finals: Array[Int] = finalsR 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Elem.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * This singleton object contains the `apply` and `unapplySeq` methods for 20 | * convenient construction and deconstruction. It is possible to deconstruct 21 | * any `Node` instance (that is not a `SpecialNode` or a `Group`) using the 22 | * syntax `case Elem(prefix, label, attribs, scope, child @ _*) => ...` 23 | */ 24 | // Note: used by the Scala compiler. 25 | object Elem { 26 | 27 | def apply(prefix: String, label: String, attributes: MetaData, scope: NamespaceBinding, minimizeEmpty: Boolean, child: Node*): Elem = 28 | new Elem(prefix, label, attributes, scope, minimizeEmpty, child: _*) 29 | 30 | def unapplySeq(n: Node): Option[(String, String, MetaData, NamespaceBinding, ScalaVersionSpecific.SeqOfNode)] = 31 | n match { 32 | case _: SpecialNode | _: Group => None 33 | case _ => Some((n.prefix, n.label, n.attributes, n.scope, n.child)) 34 | } 35 | } 36 | 37 | /** 38 | * An immutable data object representing an XML element. 39 | * 40 | * Child elements can be other [[Elem]]s or any one of the other [[Node]] types. 41 | * 42 | * XML attributes are implemented with the [[scala.xml.MetaData]] base 43 | * class. 44 | * 45 | * Optional XML namespace scope is represented by 46 | * [[scala.xml.NamespaceBinding]]. 47 | * 48 | * @param prefix namespace prefix (may be null, but not the empty string) 49 | * @param label the element name 50 | * @param attributes1 the attribute map 51 | * @param scope the scope containing the namespace bindings 52 | * @param minimizeEmpty `true` if this element should be serialized as minimized (i.e. "<el/>") when 53 | * empty; `false` if it should be written out in long form. 54 | * @param child the children of this node 55 | */ 56 | // Note: used by the Scala compiler. 57 | class Elem( 58 | override val prefix: String, 59 | override val label: String, 60 | attributes1: MetaData, 61 | override val scope: NamespaceBinding, 62 | val minimizeEmpty: Boolean, 63 | override val child: Node* 64 | ) extends Node with Serializable { 65 | 66 | final override def doCollectNamespaces: Boolean = true 67 | final override def doTransform: Boolean = true 68 | 69 | override val attributes: MetaData = MetaData.normalize(attributes1, scope) 70 | 71 | if (prefix == "") 72 | throw new IllegalArgumentException("prefix of zero length, use null instead") 73 | 74 | if (scope == null) 75 | throw new IllegalArgumentException("scope is null, use scala.xml.TopScope for empty scope") 76 | 77 | //@todo: copy the children, 78 | // setting namespace scope if necessary 79 | // cleaning adjacent text nodes if necessary 80 | 81 | override protected def basisForHashCode: Seq[Any] = 82 | prefix :: label :: attributes :: child.toList 83 | 84 | /** 85 | * Returns a new element with updated attributes, resolving namespace uris 86 | * from this element's scope. See MetaData.update for details. 87 | * 88 | * @param updates MetaData with new and updated attributes 89 | * @return a new symbol with updated attributes 90 | */ 91 | final def %(updates: MetaData): Elem = 92 | copy(attributes = MetaData.update(attributes, scope, updates)) 93 | 94 | /** 95 | * Returns a copy of this element with any supplied arguments replacing 96 | * this element's value for that field. 97 | * 98 | * @return a new symbol with updated attributes 99 | */ 100 | def copy( 101 | prefix: String = this.prefix, 102 | label: String = this.label, 103 | attributes: MetaData = this.attributes, 104 | scope: NamespaceBinding = this.scope, 105 | minimizeEmpty: Boolean = this.minimizeEmpty, 106 | child: Seq[Node] = this.child 107 | ): Elem = Elem(prefix, label, attributes, scope, minimizeEmpty, child.toSeq: _*) 108 | 109 | /** 110 | * Returns concatenation of `text(n)` for each child `n`. 111 | */ 112 | override def text: String = child.map(_.text).mkString 113 | } 114 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/Equality.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import scala.collection.Seq 17 | 18 | /** 19 | * In an attempt to contain the damage being inflicted on consistency by the 20 | * ad hoc `equals` methods spread around `xml`, the logic is centralized and 21 | * all the `xml` classes go through the `xml.Equality trait`. There are two 22 | * forms of `xml` comparison. 23 | * 24 | * 1. `'''def''' strict_==(other: scala.xml.Equality)` 25 | * 26 | * This one tries to honor the little things like symmetry and hashCode 27 | * contracts. The `equals` method routes all comparisons through this. 28 | * 29 | * 1. `xml_==(other: Any)` 30 | * 31 | * This one picks up where `strict_==` leaves off. It might declare any two 32 | * things equal. 33 | * 34 | * As things stood, the logic not only made a mockery of the collections 35 | * equals contract, but also laid waste to that of case classes. 36 | * 37 | * Among the obstacles to sanity are/were: 38 | * 39 | * Node extends NodeSeq extends Seq[Node] 40 | * MetaData extends Iterable[MetaData] 41 | * The hacky "Group" xml node which throws exceptions 42 | * with wild abandon, so don't get too close 43 | * Rampant asymmetry and impossible hashCodes 44 | * Most classes claiming to be equal to "String" if 45 | * some specific stringification of it was the same. 46 | * String was never going to return the favor. 47 | */ 48 | 49 | object Equality { 50 | def asRef(x: Any): AnyRef = x.asInstanceOf[AnyRef] 51 | 52 | /** 53 | * Note - these functions assume strict equality has already failed. 54 | */ 55 | def compareBlithely(x1: AnyRef, x2: String): Boolean = x1 match { 56 | case x: Atom[?] => x.data == x2 57 | case x: NodeSeq => x.text == x2 58 | case _ => false 59 | } 60 | def compareBlithely(x1: AnyRef, x2: Node): Boolean = x1 match { 61 | case x: NodeSeq if x.length == 1 => x2 == x(0) 62 | case _ => false 63 | } 64 | def compareBlithely(x1: AnyRef, x2: AnyRef): Boolean = 65 | if (x1 == null || x2 == null) x1 == null && x2 == null else x2 match { 66 | case s: String => compareBlithely(x1, s) 67 | case n: Node => compareBlithely(x1, n) 68 | case _ => false 69 | } 70 | } 71 | 72 | trait Equality extends scala.Equals { 73 | protected def basisForHashCode: Seq[Any] 74 | 75 | def strict_==(other: Equality): Boolean 76 | def strict_!=(other: Equality): Boolean = !strict_==(other) 77 | 78 | /** 79 | * We insist we're only equal to other `xml.Equality` implementors, 80 | * which heads off a lot of inconsistency up front. 81 | */ 82 | override def canEqual(other: Any): Boolean = other match { 83 | case _: Equality => true 84 | case _ => false 85 | } 86 | 87 | /** 88 | * It's be nice to make these final, but there are probably 89 | * people out there subclassing the XML types, especially when 90 | * it comes to equals. However WE at least can pretend they 91 | * are final since clearly individual classes cannot be trusted 92 | * to maintain a semblance of order. 93 | */ 94 | override def hashCode: Int = basisForHashCode.## 95 | override def equals(other: Any): Boolean = doComparison(other, blithe = false) 96 | final def xml_==(other: Any): Boolean = doComparison(other, blithe = true) 97 | final def xml_!=(other: Any): Boolean = !xml_==(other) 98 | 99 | /** 100 | * The "blithe" parameter expresses the caller's unconcerned attitude 101 | * regarding the usual constraints on equals. The method is thereby 102 | * given carte blanche to declare any two things equal. 103 | */ 104 | private def doComparison(other: Any, blithe: Boolean): Boolean = { 105 | val strictlyEqual: Boolean = other match { 106 | case x: AnyRef if this.eq(x) => true 107 | case x: Equality => x.canEqual(this) && this.strict_==(x) 108 | case _ => false 109 | } 110 | 111 | strictlyEqual || (blithe && Equality.compareBlithely(this, Equality.asRef(other))) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/dtd/ContentModel.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package dtd 16 | 17 | import scala.collection.Seq 18 | import scala.xml.dtd.impl._ 19 | import scala.xml.Utility.sbToString 20 | import PartialFunction._ 21 | 22 | /* 23 | @deprecated("Avoidance", since="2.10") 24 | trait ContentModelLaundry extends WordExp 25 | object ContentModelLaundry extends ContentModelLaundry { 26 | } 27 | */ 28 | 29 | object ContentModel extends WordExp { 30 | 31 | override type _labelT = ElemName 32 | override type _regexpT = RegExp 33 | 34 | @deprecated("Avoidance", since="2.10") 35 | trait Translator extends WordBerrySethi 36 | object Translator extends Translator { 37 | override val lang: ContentModel.this.type = ContentModel.this 38 | } 39 | 40 | case class ElemName(name: String) extends Label { 41 | override def toString: String = s"""ElemName("$name")""" 42 | } 43 | 44 | def isMixed(cm: ContentModel): Boolean = cond(cm) { case _: MIXED => true } 45 | def containsText(cm: ContentModel): Boolean = (cm == PCDATA) || isMixed(cm) 46 | 47 | def getLabels(r: RegExp): Set[String] = { 48 | def traverse(r: RegExp): Set[String] = r match { // !!! check for match translation problem 49 | case Letter(ElemName(name)) => Set(name) 50 | case Star(x@_) => traverse(x) // bug if x@_* 51 | case Sequ(xs@_*) => Set(xs.flatMap(traverse): _*) 52 | case Alt(xs@_*) => Set(xs.flatMap(traverse): _*) 53 | } 54 | 55 | traverse(r) 56 | } 57 | 58 | def buildString(r: RegExp): String = sbToString(buildString(r, _)) 59 | 60 | /* precond: rs.length >= 1 */ 61 | private def buildString(rs: Seq[RegExp], sb: StringBuilder, sep: Char): Unit = { 62 | buildString(rs.head, sb) 63 | for (z <- rs.tail) { 64 | sb.append(sep) 65 | buildString(z, sb) 66 | } 67 | } 68 | 69 | def buildString(c: ContentModel, sb: StringBuilder): StringBuilder = c match { 70 | case ANY => sb.append("ANY") 71 | case EMPTY => sb.append("EMPTY") 72 | case PCDATA => sb.append("(#PCDATA)") 73 | case ELEMENTS(_) | MIXED(_) => c.buildString(sb) 74 | } 75 | 76 | def buildString(r: RegExp, sb: StringBuilder): StringBuilder = 77 | r match { // !!! check for match translation problem 78 | case Eps => 79 | sb 80 | case Sequ(rs@_*) => 81 | sb.append('('); buildString(rs, sb, ','); sb.append(')') 82 | case Alt(rs@_*) => 83 | sb.append('('); buildString(rs, sb, '|'); sb.append(')') 84 | case Star(r: RegExp) => 85 | sb.append('('); buildString(r, sb); sb.append(")*") 86 | case Letter(ElemName(name)) => 87 | sb.append(name) 88 | } 89 | } 90 | 91 | sealed abstract class ContentModel { 92 | override def toString: String = sbToString(buildString) 93 | def buildString(sb: StringBuilder): StringBuilder 94 | } 95 | 96 | import ContentModel.RegExp 97 | 98 | case object PCDATA extends ContentModel { 99 | override def buildString(sb: StringBuilder): StringBuilder = sb.append("(#PCDATA)") 100 | } 101 | case object EMPTY extends ContentModel { 102 | override def buildString(sb: StringBuilder): StringBuilder = sb.append("EMPTY") 103 | } 104 | case object ANY extends ContentModel { 105 | override def buildString(sb: StringBuilder): StringBuilder = sb.append("ANY") 106 | } 107 | sealed abstract class DFAContentModel extends ContentModel { 108 | import ContentModel.{ElemName, Translator} 109 | def r: RegExp 110 | 111 | lazy val dfa: DetWordAutom[ElemName] = { 112 | val nfa: NondetWordAutom[ElemName] = Translator.automatonFrom(r, 1) 113 | new SubsetConstruction(nfa).determinize 114 | } 115 | } 116 | 117 | case class MIXED(override val r: RegExp) extends DFAContentModel { 118 | import ContentModel.Alt 119 | 120 | override def buildString(sb: StringBuilder): StringBuilder = { 121 | val newAlt: Alt = r match { case Alt(rs@_*) => Alt(rs.drop(1): _*) } 122 | 123 | sb.append("(#PCDATA|") 124 | ContentModel.buildString(newAlt: RegExp, sb) 125 | sb.append(")*") 126 | } 127 | } 128 | 129 | case class ELEMENTS(override val r: RegExp) extends DFAContentModel { 130 | override def buildString(sb: StringBuilder): StringBuilder = 131 | ContentModel.buildString(r, sb) 132 | } 133 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/MarkupHandler.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | import scala.collection.mutable 18 | import scala.io.Source 19 | import scala.xml.dtd._ 20 | 21 | /** 22 | * class that handles markup - provides callback methods to MarkupParser. 23 | * the default is nonvalidating behaviour 24 | * 25 | * @author Burak Emir 26 | * @todo can we ignore more entity declarations (i.e. those with extIDs)? 27 | * @todo expanding entity references 28 | */ 29 | abstract class MarkupHandler { 30 | 31 | /** returns true is this markup handler is validating */ 32 | val isValidating: Boolean = false 33 | 34 | var decls: List[Decl] = Nil 35 | var ent: mutable.Map[String, EntityDecl] = new mutable.HashMap[String, EntityDecl]() 36 | 37 | def lookupElemDecl(Label: String): ElemDecl = 38 | (for (case z@ElemDecl(Label, _) <- decls) yield z).headOption.orNull 39 | 40 | def replacementText(entityName: String): Source = 41 | Source.fromString(ent.get(entityName) match { 42 | case Some(ParsedEntityDecl(_, IntDef(value))) => value 43 | case Some(ParameterEntityDecl(_, IntDef(value))) => s" value " 44 | case Some(_) => s"" 45 | case None => s"" 46 | }) 47 | 48 | def endDTD(n: String): Unit = () 49 | 50 | /** 51 | * callback method invoked by MarkupParser after start-tag of element. 52 | * 53 | * @param pos the position in the sourcefile 54 | * @param pre the prefix 55 | * @param label the local name 56 | * @param attrs the attributes (metadata) 57 | */ 58 | def elemStart(pos: Int, pre: String, label: String, attrs: MetaData, scope: NamespaceBinding): Unit = () 59 | 60 | /** 61 | * callback method invoked by MarkupParser after end-tag of element. 62 | * 63 | * @param pos the position in the source file 64 | * @param pre the prefix 65 | * @param label the local name 66 | */ 67 | def elemEnd(pos: Int, pre: String, label: String): Unit = () 68 | 69 | /** 70 | * callback method invoked by MarkupParser after parsing an element, 71 | * between the elemStart and elemEnd callbacks 72 | * 73 | * @param pos the position in the source file 74 | * @param pre the prefix 75 | * @param label the local name 76 | * @param attrs the attributes (metadata) 77 | * @param empty `true` if the element was previously empty; `false` otherwise. 78 | * @param args the children of this element 79 | */ 80 | def elem(pos: Int, pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, empty: Boolean, args: NodeSeq): NodeSeq 81 | 82 | /** 83 | * callback method invoked by MarkupParser after parsing PI. 84 | */ 85 | def procInstr(pos: Int, target: String, txt: String): NodeSeq 86 | 87 | /** 88 | * callback method invoked by MarkupParser after parsing comment. 89 | */ 90 | def comment(pos: Int, comment: String): NodeSeq 91 | 92 | /** 93 | * callback method invoked by MarkupParser after parsing entity ref. 94 | * @todo expanding entity references 95 | */ 96 | def entityRef(pos: Int, n: String): NodeSeq 97 | 98 | /** 99 | * callback method invoked by MarkupParser after parsing text. 100 | */ 101 | def text(pos: Int, txt: String): NodeSeq 102 | 103 | // DTD handler methods 104 | 105 | def elemDecl(n: String, cmstr: String): Unit = () 106 | 107 | def attListDecl(name: String, attList: List[AttrDecl]): Unit = () 108 | 109 | private def someEntityDecl(name: String, edef: EntityDef, f: (String, EntityDef) => EntityDecl): Unit = 110 | edef match { 111 | case _: ExtDef if !isValidating => // ignore (cf REC-xml 4.4.1) 112 | case _ => 113 | val y: EntityDecl = f(name, edef) 114 | decls ::= y 115 | ent.update(name, y) 116 | } 117 | 118 | def parameterEntityDecl(name: String, edef: EntityDef): Unit = 119 | someEntityDecl(name, edef, ParameterEntityDecl.apply) 120 | 121 | def parsedEntityDecl(name: String, edef: EntityDef): Unit = 122 | someEntityDecl(name, edef, ParsedEntityDecl.apply) 123 | 124 | def peReference(name: String): Unit = { decls ::= PEReference(name) } 125 | def unparsedEntityDecl(name: String, extID: ExternalID, notat: String): Unit = () 126 | def notationDecl(notat: String, extID: ExternalID): Unit = () 127 | def reportSyntaxError(pos: Int, str: String): Unit 128 | } 129 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/parsing/XhtmlEntities.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package parsing 16 | 17 | import scala.xml.dtd.{ IntDef, ParsedEntityDecl } 18 | 19 | /** 20 | * @author (c) David Pollak 2007 WorldWide Conferencing, LLC. 21 | * 22 | */ 23 | object XhtmlEntities { 24 | val entList: List[(String, Int)] = List(("quot", 34), ("amp", 38), ("lt", 60), ("gt", 62), ("nbsp", 160), ("iexcl", 161), ("cent", 162), ("pound", 163), ("curren", 164), ("yen", 165), 25 | ("euro", 8364), ("brvbar", 166), ("sect", 167), ("uml", 168), ("copy", 169), ("ordf", 170), ("laquo", 171), ("shy", 173), ("reg", 174), ("trade", 8482), 26 | ("macr", 175), ("deg", 176), ("plusmn", 177), ("sup2", 178), ("sup3", 179), ("acute", 180), ("micro", 181), ("para", 182), ("middot", 183), ("cedil", 184), 27 | ("sup1", 185), ("ordm", 186), ("raquo", 187), ("frac14", 188), ("frac12", 189), ("frac34", 190), ("iquest", 191), ("times", 215), ("divide", 247), 28 | ("Agrave", 192), ("Aacute", 193), ("Acirc", 194), ("Atilde", 195), ("Auml", 196), ("Aring", 197), ("AElig", 198), ("Ccedil", 199), ("Egrave", 200), 29 | ("Eacute", 201), ("Ecirc", 202), ("Euml", 203), ("Igrave", 204), ("Iacute", 205), ("Icirc", 206), ("Iuml", 207), ("ETH", 208), ("Ntilde", 209), 30 | ("Ograve", 210), ("Oacute", 211), ("Ocirc", 212), ("Otilde", 213), ("Ouml", 214), ("Oslash", 216), ("Ugrave", 217), ("Uacute", 218), ("Ucirc", 219), 31 | ("Uuml", 220), ("Yacute", 221), ("THORN", 222), ("szlig", 223), ("agrave", 224), ("aacute", 225), ("acirc", 226), ("atilde", 227), ("auml", 228), 32 | ("aring", 229), ("aelig", 230), ("ccedil", 231), ("egrave", 232), ("eacute", 233), ("ecirc", 234), ("euml", 235), ("igrave", 236), ("iacute", 237), 33 | ("icirc", 238), ("iuml", 239), ("eth", 240), ("ntilde", 241), ("ograve", 242), ("oacute", 243), ("ocirc", 244), ("otilde", 245), ("ouml", 246), 34 | ("oslash", 248), ("ugrave", 249), ("uacute", 250), ("ucirc", 251), ("uuml", 252), ("yacute", 253), ("thorn", 254), ("yuml", 255), ("OElig", 338), 35 | ("oelig", 339), ("Scaron", 352), ("scaron", 353), ("Yuml", 376), ("circ", 710), ("ensp", 8194), ("emsp", 8195), ("zwnj", 204), ("zwj", 8205), ("lrm", 8206), 36 | ("rlm", 8207), ("ndash", 8211), ("mdash", 8212), ("lsquo", 8216), ("rsquo", 8217), ("sbquo", 8218), ("ldquo", 8220), ("rdquo", 8221), ("bdquo", 8222), 37 | ("dagger", 8224), ("Dagger", 8225), ("permil", 8240), ("lsaquo", 8249), ("rsaquo", 8250), ("fnof", 402), ("bull", 8226), ("hellip", 8230), ("prime", 8242), 38 | ("Prime", 8243), ("oline", 8254), ("frasl", 8260), ("weierp", 8472), ("image", 8465), ("real", 8476), ("alefsym", 8501), ("larr", 8592), ("uarr", 8593), 39 | ("rarr", 8594), ("darr", 8595), ("harr", 8596), ("crarr", 8629), ("lArr", 8656), ("uArr", 8657), ("rArr", 8658), ("dArr", 8659), ("hArr", 8660), 40 | ("forall", 8704), ("part", 8706), ("exist", 8707), ("empty", 8709), ("nabla", 8711), ("isin", 8712), ("notin", 8713), ("ni", 8715), ("prod", 8719), 41 | ("sum", 8721), ("minus", 8722), ("lowast", 8727), ("radic", 8730), ("prop", 8733), ("infin", 8734), ("ang", 8736), ("and", 8743), ("or", 8744), 42 | ("cap", 8745), ("cup", 8746), ("int", 8747), ("there4", 8756), ("sim", 8764), ("cong", 8773), ("asymp", 8776), ("ne", 8800), ("equiv", 8801), ("le", 8804), 43 | ("ge", 8805), ("sub", 8834), ("sup", 8835), ("nsub", 8836), ("sube", 8838), ("supe", 8839), ("oplus", 8853), ("otimes", 8855), ("perp", 8869), ("sdot", 8901), 44 | ("lceil", 8968), ("rceil", 8969), ("lfloor", 8970), ("rfloor", 8971), ("lang", 9001), ("rang", 9002), ("loz", 9674), ("spades", 9824), ("clubs", 9827), 45 | ("hearts", 9829), ("diams", 9830), ("Alpha", 913), ("Beta", 914), ("Gamma", 915), ("Delta", 916), ("Epsilon", 917), ("Zeta", 918), ("Eta", 919), 46 | ("Theta", 920), ("Iota", 921), ("Kappa", 922), ("Lambda", 923), ("Mu", 924), ("Nu", 925), ("Xi", 926), ("Omicron", 927), ("Pi", 928), ("Rho", 929), 47 | ("Sigma", 931), ("Tau", 932), ("Upsilon", 933), ("Phi", 934), ("Chi", 935), ("Psi", 936), ("Omega", 937), ("alpha", 945), ("beta", 946), ("gamma", 947), 48 | ("delta", 948), ("epsilon", 949), ("zeta", 950), ("eta", 951), ("theta", 952), ("iota", 953), ("kappa", 954), ("lambda", 955), ("mu", 956), ("nu", 957), 49 | ("xi", 958), ("omicron", 959), ("pi", 960), ("rho", 961), ("sigmaf", 962), ("sigma", 963), ("tau", 964), ("upsilon", 965), ("phi", 966), ("chi", 967), 50 | ("psi", 968), ("omega", 969), ("thetasym", 977), ("upsih", 978), ("piv", 982)) 51 | 52 | val entMap: Map[String, Char] = Map.empty[String, Char] ++ entList.map { case (name, value) => (name, value.toChar) } 53 | 54 | val entities: List[(String, ParsedEntityDecl)] = entList. 55 | map { case (name, value) => (name, ParsedEntityDecl(name, IntDef(value.toChar.toString))) } 56 | 57 | def apply(): List[(String, ParsedEntityDecl)] = entities 58 | } 59 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/XML.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | 16 | import factory.XMLLoader 17 | import java.io.{File, FileDescriptor, FileInputStream, FileOutputStream, InputStream, Reader, StringReader, Writer} 18 | import java.nio.channels.Channels 19 | import scala.util.control.Exception 20 | 21 | object Source { 22 | def fromFile(name: String): InputSource = fromFile(new File(name)) 23 | def fromFile(file: File): InputSource = fromUrl(file.toURI.toURL) 24 | def fromUrl(url: java.net.URL): InputSource = fromSysId(url.toString) 25 | def fromSysId(sysID: String): InputSource = new InputSource(sysID) 26 | def fromFile(fd: FileDescriptor): InputSource = fromInputStream(new FileInputStream(fd)) 27 | def fromInputStream(is: InputStream): InputSource = new InputSource(is) 28 | def fromString(string: String): InputSource = fromReader(new StringReader(string)) 29 | def fromReader(reader: Reader): InputSource = new InputSource(reader) 30 | } 31 | 32 | /** 33 | * Governs how empty elements (i.e. those without child elements) should be serialized. 34 | */ 35 | object MinimizeMode extends Enumeration { 36 | /** 37 | * Minimize empty tags if they were originally empty when parsed, or if they were constructed 38 | * with [[scala.xml.Elem]]`#minimizeEmpty` == true 39 | */ 40 | val Default: Value = Value 41 | 42 | /** 43 | * Always minimize empty tags. Note that this may be problematic for XHTML, in which 44 | * case [[scala.xml.Xhtml]]`#toXhtml` should be used instead. 45 | */ 46 | val Always: Value = Value 47 | 48 | /** 49 | * Never minimize empty tags. 50 | */ 51 | val Never: Value = Value 52 | } 53 | 54 | /** 55 | * The object `XML` provides constants, and functions to load 56 | * and save XML elements. Use this when data binding is not desired, i.e. 57 | * when XML is handled using `Symbol` nodes. 58 | * 59 | * @author Burak Emir 60 | */ 61 | object XML extends XMLLoader[Elem] { 62 | val xml: String = "xml" 63 | val xmlns: String = "xmlns" 64 | val namespace: String = "http://www.w3.org/XML/1998/namespace" 65 | val preserve: String = "preserve" 66 | val space: String = "space" 67 | val lang: String = "lang" 68 | val encoding: String = "UTF-8" 69 | 70 | /** Returns an XMLLoader whose load* methods will use the supplied SAXParser. */ 71 | def withSAXParser(p: SAXParser): XMLLoader[Elem] = new XMLLoader[Elem] { 72 | override val parser: SAXParser = p 73 | } 74 | 75 | /** Returns an XMLLoader whose load* methods will use the supplied XMLReader. */ 76 | def withXMLReader(r: XMLReader): XMLLoader[Elem] = new XMLLoader[Elem] { 77 | override val reader: XMLReader = r 78 | } 79 | 80 | /** 81 | * Saves a node to a file with given filename using given encoding 82 | * optionally with xmldecl and doctype declaration. 83 | * 84 | * Note: Before scala-xml 1.1.0, the default encoding was ISO-8859-1 (latin1). 85 | * If your code depends on characters in non-ASCII latin1 range, specify 86 | * ISO-8859-1 encoding explicitly. 87 | * 88 | * @param filename the filename 89 | * @param node the xml node we want to write 90 | * @param enc encoding to use 91 | * @param xmlDecl if true, write xml declaration 92 | * @param doctype if not null, write doctype declaration 93 | */ 94 | final def save( 95 | filename: String, 96 | node: Node, 97 | enc: String = "UTF-8", 98 | xmlDecl: Boolean = false, 99 | doctype: dtd.DocType = null 100 | ): Unit = { 101 | val fos: FileOutputStream = new FileOutputStream(filename) 102 | val w: Writer = Channels.newWriter(fos.getChannel, enc) 103 | 104 | Exception.ultimately(w.close())( 105 | write(w, node, enc, xmlDecl, doctype) 106 | ) 107 | } 108 | 109 | /** 110 | * Writes the given node using writer, optionally with xml decl and doctype. 111 | * It's the caller's responsibility to close the writer. 112 | * 113 | * @param w the writer 114 | * @param node the xml node we want to write 115 | * @param enc the string to be used in `xmlDecl` 116 | * @param xmlDecl if true, write xml declaration 117 | * @param doctype if not null, write doctype declaration 118 | */ 119 | final def write( 120 | w: Writer, 121 | node: Node, 122 | enc: String, 123 | xmlDecl: Boolean, 124 | doctype: dtd.DocType, 125 | minimizeTags: MinimizeMode.Value = MinimizeMode.Default 126 | ): Unit = { 127 | /* TODO: optimize by giving writer parameter to toXML*/ 128 | if (xmlDecl) w.write(s"\n") 129 | if (doctype != null) w.write(s"$doctype\n") 130 | w.write(Utility.serialize(node, minimizeTags = minimizeTags).toString) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | executors: 4 | scala_jdk8_executor: 5 | docker: 6 | - image: cimg/openjdk:8.0-node 7 | resource_class: small 8 | scala_jdk11_executor: 9 | docker: 10 | - image: cimg/openjdk:11.0-node 11 | resource_class: small 12 | scala_jdk17_executor: 13 | docker: 14 | - image: cimg/openjdk:17.0-node 15 | resource_class: small 16 | scala_jdk21_executor: 17 | docker: 18 | - image: cimg/openjdk:21.0-node 19 | resource_class: small 20 | 21 | commands: 22 | sbt_cmd: 23 | description: "Build with sbt" 24 | parameters: 25 | scala_version: 26 | type: string 27 | default: 2.12.18 28 | sbt_tasks: 29 | type: string 30 | default: update compile test:compile test doc package osgiBundle 31 | steps: 32 | - restore_cache: 33 | keys: 34 | - sbt-deps-v1-{{ checksum "build.sbt" }} 35 | - sbt-deps-v1- 36 | - run: sbt -Dsbt.io.jdktimestamps=true ++<< parameters.scala_version >> << parameters.sbt_tasks >> 37 | - save_cache: 38 | key: sbt-deps-v1-{{ checksum "build.sbt" }} 39 | paths: 40 | - "~/.cache/coursier" 41 | - "~/.ivy2/cache" 42 | - "~/.sbt" 43 | - "~/.m2" 44 | 45 | jobs: 46 | scala_job: 47 | executor: scala_<>_executor 48 | parameters: 49 | scala_version: 50 | description: "Scala version" 51 | default: 2.12.18 52 | type: string 53 | java_version: 54 | description: "Java version" 55 | default: jdk8 56 | type: string 57 | steps: 58 | - checkout 59 | - run: java -version 60 | - sbt_cmd: 61 | scala_version: << parameters.scala_version >> 62 | sbt_tasks: xml/update xml/compile xml/Test/compile xml/test xml/doc xml/package xml/osgiBundle 63 | scalajs_job: 64 | executor: scala_jdk8_executor 65 | parameters: 66 | scala_version: 67 | description: "Scala version" 68 | default: 2.12.18 69 | type: string 70 | steps: 71 | - checkout 72 | - run: java -version 73 | - run: node -v 74 | - sbt_cmd: 75 | scala_version: << parameters.scala_version >> 76 | sbt_tasks: xmlJS/update xmlJS/compile xmlJS/Test/compile xmlJS/test xmlJS/doc xmlJS/package 77 | scalanative_job: 78 | executor: scala_jdk8_executor 79 | parameters: 80 | scala_version: 81 | description: "Scala version" 82 | default: 2.12.18 83 | type: string 84 | steps: 85 | - checkout 86 | - run: 87 | name: Install dependencies 88 | command: | 89 | sudo apt-get update 90 | sudo apt-get install -y clang 91 | - sbt_cmd: 92 | scala_version: << parameters.scala_version >> 93 | sbt_tasks: xmlNative/update xmlNative/compile xmlNative/test:compile xmlNative/test xmlNative/doc xmlNative/package 94 | 95 | workflows: 96 | build: 97 | jobs: 98 | - scala_job: 99 | name: 2.12.x 100 | java_version: jdk8 101 | scala_version: 2.12.21 102 | - scala_job: 103 | name: 2.13.x 104 | java_version: jdk8 105 | scala_version: 2.13.18 106 | - scala_job: 107 | name: 3.x 108 | java_version: jdk8 109 | scala_version: 3.3.7 110 | - scala_job: 111 | name: jdk11_2.12.x 112 | java_version: jdk11 113 | scala_version: 2.12.21 114 | - scala_job: 115 | name: jdk11_2.13.x 116 | java_version: jdk11 117 | scala_version: 2.13.18 118 | - scala_job: 119 | name: jdk11_3.x 120 | java_version: jdk11 121 | scala_version: 3.3.7 122 | - scala_job: 123 | name: jdk17_2.12.x 124 | java_version: jdk17 125 | scala_version: 2.12.21 126 | - scala_job: 127 | name: jdk17_2.13.x 128 | java_version: jdk17 129 | scala_version: 2.13.18 130 | - scala_job: 131 | name: jdk17_3.x 132 | java_version: jdk17 133 | scala_version: 3.3.7 134 | - scala_job: 135 | name: jdk21_2.12.x 136 | java_version: jdk21 137 | scala_version: 2.12.21 138 | - scala_job: 139 | name: jdk21_2.13.x 140 | java_version: jdk21 141 | scala_version: 2.13.18 142 | - scala_job: 143 | name: jdk21_3.x 144 | java_version: jdk21 145 | scala_version: 3.3.7 146 | - scalajs_job: 147 | name: sjs1.0_2.12.x 148 | scala_version: 2.12.21 149 | - scalajs_job: 150 | name: sjs1.0_2.13.x 151 | scala_version: 2.13.18 152 | - scalajs_job: 153 | name: sjs1.0_3.x 154 | scala_version: 3.3.7 155 | - scalanative_job: 156 | name: native0.4_2.12.x 157 | scala_version: 2.12.21 158 | - scalanative_job: 159 | name: native0.4_2.13.x 160 | scala_version: 2.13.18 161 | - scalanative_job: 162 | name: native0.4_3.x 163 | scala_version: 3.3.7 164 | -------------------------------------------------------------------------------- /jvm/src/test/scala-2.x/scala/xml/XMLTestJVM2x.scala: -------------------------------------------------------------------------------- 1 | package scala.xml 2 | 3 | import org.junit.{Test => UnitTest} 4 | import org.junit.Assert.assertEquals 5 | import scala.xml.parsing.ConstructingParser 6 | 7 | class XMLTestJVM2x { 8 | @UnitTest 9 | def t2354(): Unit = { 10 | val xml_good: String = "<![CDATA[Hello [tag]]]>" 11 | val xml_bad: String = "<![CDATA[Hello [tag] ]]>" 12 | 13 | val parser1: ConstructingParser = ConstructingParser.fromSource(io.Source.fromString(xml_good), preserveWS = false) 14 | val parser2: ConstructingParser = ConstructingParser.fromSource(io.Source.fromString(xml_bad), preserveWS = false) 15 | 16 | parser1.document() 17 | parser2.document() 18 | } 19 | 20 | @UnitTest 21 | def t8253(): Unit = { 22 | // `identity(foo)` used to match the overly permissive match in SymbolXMLBuilder 23 | // which was intended to more specifically match `_root_.scala.xml.Text(...)` 24 | 25 | import reflect.runtime.universe._ // not using the XML library in compiler tests 26 | 27 | val ns1: String = "ns1" 28 | assertEquals(reify(ns1).tree.toString, q"ns1".toString) 29 | assertEquals("", 30 | """|{ 31 | | var $tmpscope: _root_.scala.xml.NamespaceBinding = $scope; 32 | | $tmpscope = new _root_.scala.xml.NamespaceBinding(null, "ns1", $tmpscope); 33 | | { 34 | | val $scope: _root_.scala.xml.NamespaceBinding = $tmpscope; 35 | | new _root_.scala.xml.Elem(null, "sample", _root_.scala.xml.Null, $scope, true) 36 | | } 37 | |}""".stripMargin, 38 | q"".toString) 39 | assertEquals("", 40 | """|{ 41 | | var $tmpscope: _root_.scala.xml.NamespaceBinding = $scope; 42 | | $tmpscope = new _root_.scala.xml.NamespaceBinding(null, ns1, $tmpscope); 43 | | { 44 | | val $scope: _root_.scala.xml.NamespaceBinding = $tmpscope; 45 | | new _root_.scala.xml.Elem(null, "sample", _root_.scala.xml.Null, $scope, true) 46 | | } 47 | |}""".stripMargin, 48 | q"".toString) 49 | assertEquals("", 50 | """|{ 51 | | var $tmpscope: _root_.scala.xml.NamespaceBinding = $scope; 52 | | $tmpscope = new _root_.scala.xml.NamespaceBinding("foo", "ns1", $tmpscope); 53 | | { 54 | | val $scope: _root_.scala.xml.NamespaceBinding = $tmpscope; 55 | | new _root_.scala.xml.Elem(null, "sample", _root_.scala.xml.Null, $scope, true) 56 | | } 57 | |}""".stripMargin, 58 | q"".toString) 59 | assertEquals("", 60 | """|{ 61 | | var $tmpscope: _root_.scala.xml.NamespaceBinding = $scope; 62 | | $tmpscope = new _root_.scala.xml.NamespaceBinding("foo", ns1, $tmpscope); 63 | | { 64 | | val $scope: _root_.scala.xml.NamespaceBinding = $tmpscope; 65 | | new _root_.scala.xml.Elem(null, "sample", _root_.scala.xml.Null, $scope, true) 66 | | } 67 | |}""".stripMargin, 68 | q"".toString) 69 | } 70 | 71 | @UnitTest 72 | def t8466lift(): Unit = { 73 | import scala.reflect.runtime.universe._ 74 | 75 | implicit val liftXmlComment: Liftable[Comment] = Liftable[Comment] { comment => 76 | q"new _root_.scala.xml.Comment(${comment.commentText})" 77 | } 78 | liftXmlComment(Comment("foo")) 79 | assertEquals(q"${Comment("foo")}".toString, q"".toString) 80 | } 81 | 82 | @UnitTest 83 | def t8466unlift(): Unit = { 84 | import scala.reflect.runtime.universe._ 85 | 86 | implicit val unliftXmlComment: Unliftable[Comment] = Unliftable[Comment] { 87 | case q"new _root_.scala.xml.Comment(${value: String})" => Comment(value) 88 | } 89 | unliftXmlComment.unapply(q"") 90 | val q"${comment: Comment}" = q"" 91 | assertEquals(comment.commentText, "foo") 92 | } 93 | 94 | @UnitTest 95 | def t9027(): Unit = { 96 | // used to be parsed as .println 97 | 98 | import reflect.runtime._, universe._ 99 | 100 | assertEquals( 101 | """|{ 102 | | { 103 | | val $buf = new _root_.scala.xml.NodeBuffer(); 104 | | $buf.$amp$plus(new _root_.scala.xml.Elem(null, "a", _root_.scala.xml.Null, $scope, true)); 105 | | $buf.$amp$plus(new _root_.scala.xml.Elem(null, "b", _root_.scala.xml.Null, $scope, true)); 106 | | $buf 107 | | }; 108 | | println("hello, world.") 109 | |}""".stripMargin, 110 | q""" 111 | println("hello, world.")""".toString) 112 | assertEquals( 113 | """|{ 114 | | { 115 | | val $buf = new _root_.scala.xml.NodeBuffer(); 116 | | $buf.$amp$plus(new _root_.scala.xml.Elem(null, "a", _root_.scala.xml.Null, $scope, true)); 117 | | $buf.$amp$plus(new _root_.scala.xml.Elem(null, "b", _root_.scala.xml.Null, $scope, true)); 118 | | $buf.$amp$plus(new _root_.scala.xml.Elem(null, "c", _root_.scala.xml.Null, $scope, true)); 119 | | $buf 120 | | }; 121 | | println("hello, world.") 122 | |}""".stripMargin, 123 | q""" 124 | 125 | 126 | println("hello, world.")""".toString) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /shared/src/main/scala/scala/xml/factory/XMLLoader.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala 14 | package xml 15 | package factory 16 | 17 | import org.xml.sax.XMLReader 18 | import scala.xml.Source 19 | import javax.xml.parsers.SAXParserFactory 20 | import java.io.{File, FileDescriptor, InputStream, Reader} 21 | import java.net.URL 22 | 23 | /** 24 | * Presents collection of XML loading methods which use the parser 25 | * created by "def parser" or the reader created by "def reader". 26 | */ 27 | trait XMLLoader[T <: Node] { 28 | private def setSafeDefaults(parserFactory: SAXParserFactory): Unit = { 29 | parserFactory.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true) 30 | parserFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) 31 | parserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) 32 | parserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false) 33 | parserFactory.setFeature("http://xml.org/sax/features/external-general-entities", false) 34 | parserFactory.setFeature("http://xml.org/sax/features/resolve-dtd-uris", false) 35 | parserFactory.setXIncludeAware(false) 36 | parserFactory.setNamespaceAware(false) 37 | } 38 | 39 | private lazy val parserInstance: ThreadLocal[SAXParser] = new ThreadLocal[SAXParser] { 40 | override def initialValue: SAXParser = { 41 | val parserFactory: SAXParserFactory = SAXParserFactory.newInstance 42 | setSafeDefaults(parserFactory) 43 | parserFactory.newSAXParser 44 | } 45 | } 46 | 47 | /* Override this to use a different SAXParser. */ 48 | def parser: SAXParser = { 49 | val p = parserInstance.get 50 | try { p.reset() } catch { case _: UnsupportedOperationException => } 51 | p 52 | } 53 | 54 | /* Override this to use a different XMLReader. */ 55 | def reader: XMLReader = parser.getXMLReader 56 | 57 | /** 58 | * Loads XML from the given InputSource, using the supplied parser or reader. 59 | * The methods available in scala.xml.XML use the XML parser in the JDK 60 | * (unless another parser is present on the classpath). 61 | */ 62 | 63 | // TODO remove 64 | def loadXML(inputSource: InputSource, parser: SAXParser): T = getDocElem(adapter.loadDocument(inputSource, parser.getXMLReader)) 65 | def loadXMLNodes(inputSource: InputSource, parser: SAXParser): Seq[Node] = adapter.loadDocument(inputSource, parser.getXMLReader).children.toSeq 66 | def adapter: parsing.FactoryAdapter = new parsing.NoBindingFactoryAdapter() 67 | 68 | /** Loads XML Document. */ 69 | def loadDocument(inputSource: InputSource): Document = adapter.loadDocument(inputSource, reader) 70 | def loadFileDocument(fileName: String): Document = loadDocument(Source.fromFile(fileName)) 71 | def loadFileDocument(file: File): Document = loadDocument(Source.fromFile(file)) 72 | def loadDocument(url: URL): Document = loadDocument(Source.fromUrl(url)) 73 | def loadDocument(sysId: String): Document = loadDocument(Source.fromSysId(sysId)) 74 | def loadFileDocument(fileDescriptor: FileDescriptor): Document = loadDocument(Source.fromFile(fileDescriptor)) 75 | def loadDocument(inputStream: InputStream): Document = loadDocument(Source.fromInputStream(inputStream)) 76 | def loadDocument(reader: Reader): Document = loadDocument(Source.fromReader(reader)) 77 | def loadStringDocument(string: String): Document = loadDocument(Source.fromString(string)) 78 | 79 | /** Loads XML element. */ 80 | private def getDocElem(document: Document): T = document.docElem.asInstanceOf[T] 81 | def load(inputSource: InputSource): T = getDocElem(loadDocument(inputSource)) 82 | def loadFile(fileName: String): T = getDocElem(loadFileDocument(fileName)) 83 | def loadFile(file: File): T = getDocElem(loadFileDocument(file)) 84 | def load(url: URL): T = getDocElem(loadDocument(url)) 85 | def load(sysId: String): T = getDocElem(loadDocument(sysId)) 86 | def loadFile(fileDescriptor: FileDescriptor): T = getDocElem(loadFileDocument(fileDescriptor)) 87 | def load(inputStream: InputStream): T = getDocElem(loadDocument(inputStream)) 88 | def load(reader: Reader): T = getDocElem(loadDocument(reader)) 89 | def loadString(string: String): T = getDocElem(loadStringDocument(string)) 90 | 91 | /** Load XML nodes, including comments and processing instructions that precede and follow the root element. */ 92 | def loadNodes(inputSource: InputSource): Seq[Node] = loadDocument(inputSource).children.toSeq 93 | def loadFileNodes(fileName: String): Seq[Node] = loadFileDocument(fileName).children.toSeq 94 | def loadFileNodes(file: File): Seq[Node] = loadFileDocument(file).children.toSeq 95 | def loadNodes(url: URL): Seq[Node] = loadDocument(url).children.toSeq 96 | def loadNodes(sysId: String): Seq[Node] = loadDocument(sysId).children.toSeq 97 | def loadFileNodes(fileDescriptor: FileDescriptor): Seq[Node] = loadFileDocument(fileDescriptor).children.toSeq 98 | def loadNodes(inputStream: InputStream): Seq[Node] = loadDocument(inputStream).children.toSeq 99 | def loadNodes(reader: Reader): Seq[Node] = loadDocument(reader).children.toSeq 100 | def loadStringNodes(string: String): Seq[Node] = loadStringDocument(string).children.toSeq 101 | } 102 | --------------------------------------------------------------------------------