├── .editorconfig
├── .travis.yml
├── kotlin-xml-builder
├── test.dtd
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── test-results
│ │ │ │ ├── XmlBuilderTest
│ │ │ │ ├── emptyRoot.xml
│ │ │ │ ├── emptyString.xml
│ │ │ │ ├── doctypeSimple.xml
│ │ │ │ ├── emptyElement.xml
│ │ │ │ ├── removeElement.xml
│ │ │ │ ├── updateAttribute.xml
│ │ │ │ ├── whitespace.xml
│ │ │ │ ├── addElement.xml
│ │ │ │ ├── selfClosingTag.xml
│ │ │ │ ├── xmlEncode.xml
│ │ │ │ ├── cdata.xml
│ │ │ │ ├── parseXmlEncode.xml
│ │ │ │ ├── replaceElement.xml
│ │ │ │ ├── doctypeSystem.xml
│ │ │ │ ├── addElementAfter.xml
│ │ │ │ ├── noSelfClosingTag.xml
│ │ │ │ ├── quoteInAttribute.xml
│ │ │ │ ├── addElementBefore.xml
│ │ │ │ ├── elementValue.xml
│ │ │ │ ├── parseCData.xml
│ │ │ │ ├── specialCharInAttribute.xml
│ │ │ │ ├── unsafeAttributeValue.xml
│ │ │ │ ├── addElementAfterLastChild.xml
│ │ │ │ ├── elementAsString.xml
│ │ │ │ ├── processingInstruction.xml
│ │ │ │ ├── cdataNesting.xml
│ │ │ │ ├── elementAsStringWithAttributes.xml
│ │ │ │ ├── singleLineCDATAElement.xml
│ │ │ │ ├── doctypePublic.xml
│ │ │ │ ├── notPrettyFormatting.xml
│ │ │ │ ├── zeroSpaceIndentNoPrettyFormatting.xml
│ │ │ │ ├── comment.xml
│ │ │ │ ├── singleLineTextElement.xml
│ │ │ │ ├── elementAsStringWithAttributesAndContent.xml
│ │ │ │ ├── singleLineProcessingInstructionElement.xml
│ │ │ │ ├── characterReference.xml
│ │ │ │ ├── globalProcessingInstructionElement.xml
│ │ │ │ ├── zeroSpaceIndent.xml
│ │ │ │ ├── multipleAttributes.xml
│ │ │ │ ├── parseMultipleAttributes.xml
│ │ │ │ ├── singleLineProcessingInstructionElementWithAttributes.xml
│ │ │ │ ├── parseCDataWhitespace.xml
│ │ │ │ ├── customNamespaces.xml
│ │ │ │ ├── parseCustomNamespaces.xml
│ │ │ │ ├── basicTest.xml
│ │ │ │ ├── parseBasicTest.xml
│ │ │ │ └── advancedNamespaces.xml
│ │ │ │ ├── OrderedNodesTest
│ │ │ │ └── correctOrder.xml
│ │ │ │ ├── NodeTest
│ │ │ │ ├── addElementsAfter.xml
│ │ │ │ └── addElementsBefore.xml
│ │ │ │ └── sitemapTest
│ │ │ │ ├── allElements.xml
│ │ │ │ ├── basicTest.xml
│ │ │ │ └── sitemapIndex.xml
│ │ └── kotlin
│ │ │ └── org
│ │ │ └── redundent
│ │ │ └── kotlin
│ │ │ └── xml
│ │ │ ├── OrderedNodesTest.kt
│ │ │ ├── UtilsKtTest.kt
│ │ │ ├── sitemapTest.kt
│ │ │ ├── TextElementTest.kt
│ │ │ ├── CommentTest.kt
│ │ │ ├── CDATAElementTest.kt
│ │ │ ├── ProcessingInstructionElementTest.kt
│ │ │ ├── TestBase.kt
│ │ │ ├── NodeTest.kt
│ │ │ └── XmlBuilderTest.kt
│ └── main
│ │ └── kotlin
│ │ └── org
│ │ └── redundent
│ │ └── kotlin
│ │ └── xml
│ │ ├── Unsafe.kt
│ │ ├── XmlType.kt
│ │ ├── XmlVersion.kt
│ │ ├── Attribute.kt
│ │ ├── Element.kt
│ │ ├── Namespace.kt
│ │ ├── Comment.kt
│ │ ├── Doctype.kt
│ │ ├── ProcessingInstructionElement.kt
│ │ ├── CDATAElement.kt
│ │ ├── TextElement.kt
│ │ ├── Utils.kt
│ │ ├── PrintOptions.kt
│ │ ├── Sitemap.kt
│ │ ├── XmlBuilder.kt
│ │ └── Node.kt
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle.kts
├── kotlin-xml-dsl-generator
├── src
│ ├── main
│ │ └── kotlin
│ │ │ └── org
│ │ │ └── redundent
│ │ │ └── kotlin
│ │ │ └── xml
│ │ │ └── gen
│ │ │ ├── writer
│ │ │ ├── Code.kt
│ │ │ ├── XmlAttribute.kt
│ │ │ ├── XmlEnum.kt
│ │ │ ├── XmlElement.kt
│ │ │ ├── XmlClass.kt
│ │ │ └── CodeWriter.kt
│ │ │ ├── SchemaOutline.kt
│ │ │ └── DslGenerator.kt
│ └── test
│ │ ├── resources
│ │ ├── schema
│ │ │ ├── string-to-bytearray.xsd
│ │ │ ├── referencedType.xsd
│ │ │ ├── target-namespace.xsd
│ │ │ ├── child-prop-order.xsd
│ │ │ ├── group.xsd
│ │ │ ├── string-to-bytearray.jxb
│ │ │ ├── choice.xsd
│ │ │ ├── member-functions.xsd
│ │ │ ├── nested.xsd
│ │ │ ├── duplicate-names.xsd
│ │ │ ├── extension.xsd
│ │ │ ├── attributes.xsd
│ │ │ ├── attributes-required.xsd
│ │ │ └── sitemap.xsd
│ │ └── code
│ │ │ ├── referencedType.kt
│ │ │ ├── group.kt
│ │ │ ├── target-namespace.kt
│ │ │ ├── string-to-bytearray.kt
│ │ │ ├── child-prop-order.kt
│ │ │ ├── member-functions.kt
│ │ │ ├── duplicate-names.kt
│ │ │ ├── choice.kt
│ │ │ ├── nested.kt
│ │ │ ├── extension.kt
│ │ │ ├── sitemap.kt
│ │ │ ├── attributes-required.kt
│ │ │ └── attributes.kt
│ │ └── kotlin
│ │ └── org
│ │ └── redundent
│ │ └── kotlin
│ │ └── xml
│ │ └── gen
│ │ ├── SimpleSchemaGen.kt
│ │ └── AbstractGenTest.kt
├── README.md
└── build.gradle.kts
├── .github
└── workflows
│ ├── ci.yml
│ └── deploy.yml
├── gradlew.bat
├── gradlew
├── LICENSE
└── README.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{kt,kts}]
2 | indent_style = tab
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk8
--------------------------------------------------------------------------------
/kotlin-xml-builder/test.dtd:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/emptyRoot.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/emptyString.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/doctypeSimple.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/emptyElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/removeElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/updateAttribute.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/whitespace.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/addElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/selfClosingTag.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/xmlEncode.xml:
--------------------------------------------------------------------------------
1 |
2 | &<>
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/cdata.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/parseXmlEncode.xml:
--------------------------------------------------------------------------------
1 |
2 | &<>
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/replaceElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/doctypeSystem.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redundent/kotlin-xml-builder/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/OrderedNodesTest/correctOrder.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/addElementAfter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/noSelfClosingTag.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/quoteInAttribute.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/addElementBefore.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/elementValue.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | value
4 |
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/parseCData.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/specialCharInAttribute.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/unsafeAttributeValue.xml:
--------------------------------------------------------------------------------
1 |
2 | {
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/addElementAfterLastChild.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/elementAsString.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | value
4 |
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Unsafe.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | class Unsafe(val value: Any?)
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/processingInstruction.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "kotlin-xml-builder"
2 |
3 | include(":kotlin-xml-dsl-generator")
4 | include(":kotlin-xml-builder")
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/cdataNesting.xml:
--------------------------------------------------------------------------------
1 |
2 | ]]>
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/elementAsStringWithAttributes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/singleLineCDATAElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/doctypePublic.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/notPrettyFormatting.xml:
--------------------------------------------------------------------------------
1 | HelloTest
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/XmlType.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | annotation class XmlType(val childOrder: Array)
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/zeroSpaceIndentNoPrettyFormatting.xml:
--------------------------------------------------------------------------------
1 | HelloTest
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/comment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | value
5 |
6 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/singleLineTextElement.xml:
--------------------------------------------------------------------------------
1 |
2 | Hello
3 | Test
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/elementAsStringWithAttributesAndContent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Content
4 |
5 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/singleLineProcessingInstructionElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/characterReference.xml:
--------------------------------------------------------------------------------
1 |
2 | Hello & Goodbye
3 | Test
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/globalProcessingInstructionElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/zeroSpaceIndent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello
4 |
5 |
6 | Test
7 |
8 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/NodeTest/addElementsAfter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/NodeTest/addElementsBefore.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/multipleAttributes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/XmlVersion.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | enum class XmlVersion(val value: String) {
4 | V10("1.0"),
5 | V11("1.1")
6 | }
7 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/parseMultipleAttributes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/writer/Code.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen.writer
2 |
3 | interface Code {
4 | fun write(codeWriter: CodeWriter)
5 | }
6 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/singleLineProcessingInstructionElementWithAttributes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Attribute.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | data class Attribute @JvmOverloads constructor(
4 | val name: String,
5 | val value: Any,
6 | val namespace: Namespace? = null
7 | )
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/parseCDataWhitespace.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/customNamespaces.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Test
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/parseCustomNamespaces.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Test
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/basicTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | https://google.com/0
5 |
6 |
7 |
8 |
9 | https://google.com/1
10 |
11 |
12 |
13 |
14 | https://google.com/2
15 |
16 |
17 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/parseBasicTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://google.com/0
5 |
6 |
7 |
8 |
9 | http://google.com/1
10 |
11 |
12 |
13 |
14 | http://google.com/2
15 |
16 |
17 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/sitemapTest/allElements.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://blog.redundent.org
5 |
6 |
7 | 2017-10-24
8 |
9 |
10 | hourly
11 |
12 |
13 | 14.0
14 |
15 |
16 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/string-to-bytearray.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/XmlBuilderTest/advancedNamespaces.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/sitemapTest/basicTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://blog.redundent.org/post/1
5 |
6 |
7 |
8 |
9 | http://blog.redundent.org/post/2
10 |
11 |
12 |
13 |
14 | http://blog.redundent.org/post/3
15 |
16 |
17 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Element.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | /**
4 | * Base interface for all elements. You shouldn't have to interact with this interface directly.
5 | * @author Jason Blackwell
6 | */
7 | interface Element {
8 | /**
9 | * This method handles creating the XML. Used internally.
10 | */
11 | fun render(builder: Appendable, indent: String, printOptions: PrintOptions)
12 | }
13 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/referencedType.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/target-namespace.xsd:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/referencedType.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `RootType`(nodeName: String) : Node(nodeName) {
8 | var `id`: kotlin.String?
9 | get() = get("id")
10 | set(value) { set("id", value) }
11 | }
12 |
13 | fun `RootType`.`comments`(value: kotlin.String) {
14 | "comments"(value)
15 | }
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/child-prop-order.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/group.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `Root`(nodeName: String) : Node(nodeName)
8 |
9 | fun `Root`.`name`(value: kotlin.String) {
10 | "name"(value)
11 | }
12 |
13 | fun `root`(__block__: `Root`.() -> Unit): `Root` {
14 | val `root` = `Root`("root")
15 | `root`.apply(__block__)
16 | return `root`
17 | }
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/group.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/writer/XmlAttribute.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen.writer
2 |
3 | class XmlAttribute(
4 | val name: String,
5 | val type: String,
6 | private val isRequired: Boolean
7 | ) : Code {
8 | override fun write(codeWriter: CodeWriter) {
9 | codeWriter.writeln("var `$name`: $type")
10 | codeWriter.indent()
11 | codeWriter.writeln("get() = get(\"$name\")${if (isRequired) "!!" else ""}")
12 | codeWriter.writeln("set(value) { set(\"$name\", value) }")
13 | codeWriter.dedent()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/resources/test-results/sitemapTest/sitemapIndex.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://blog.redundent.org/sitemap1.xml
5 |
6 |
7 | 2017-10-24
8 |
9 |
10 |
11 |
12 | http://blog.redundent.org/sitemap2.xml
13 |
14 |
15 | 2016-01-01
16 |
17 |
18 |
19 |
20 | http://blog.redundent.org/sitemap3.xml
21 |
22 |
23 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/writer/XmlEnum.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen.writer
2 |
3 | import com.sun.tools.xjc.outline.EnumOutline
4 |
5 | class XmlEnum(private val enum: EnumOutline) : Code {
6 | override fun write(codeWriter: CodeWriter) {
7 | with(codeWriter) {
8 | writeKotlinDoc(enum.target.documentation)
9 | writeln("enum class `${enum.target.shortName}` {")
10 | indent()
11 |
12 | writeln(enum.constants.joinToString(",\n\t") { "`${it.target.lexicalValue}`" })
13 | dedent()
14 | writeln("}\n")
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/string-to-bytearray.jxb:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/choice.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Namespace.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | /**
4 | * Represents an xml namespace (`xmlns`).
5 | */
6 | data class Namespace(
7 | /**
8 | * The name or prefix of the namespace.
9 | */
10 | val name: String,
11 | /**
12 | * The value/uri/url of the namespace.
13 | */
14 | val value: String
15 | ) {
16 |
17 | constructor(value: String) : this("", value)
18 |
19 | val isDefault: Boolean = name.isEmpty()
20 |
21 | val fqName: String = if (isDefault) "xmlns" else "xmlns:$name"
22 |
23 | override fun toString(): String {
24 | return "$fqName=\"$value\""
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/OrderedNodesTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 |
5 | class OrderedNodesTest : TestBase() {
6 | @Test
7 | fun correctOrder() {
8 | val xml = structured {
9 | second()
10 | first()
11 | }
12 |
13 | validate(xml)
14 | }
15 |
16 | @XmlType(childOrder = ["first", "second"])
17 | inner class Structured internal constructor() : Node("xml") {
18 | fun first() = "first"()
19 | fun second() = "second"()
20 | }
21 |
22 | private fun structured(block: Structured.() -> Unit): Structured {
23 | return Structured().apply(block)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/member-functions.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/UtilsKtTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 |
6 | class UtilsKtTest {
7 | @Test
8 | fun escapeValue10() {
9 | val unescapedValue = "\u000b\u000c"
10 |
11 | val escapedValue = escapeValue(unescapedValue, XmlVersion.V10)
12 |
13 | assertEquals("", escapedValue, "1.0 escapes \\u000b and \\u000c to empty string")
14 | }
15 |
16 | @Test
17 | fun escapeValue11() {
18 | val unescapedValue = "\u000b\u000c"
19 |
20 | val escapedValue = escapeValue(unescapedValue, XmlVersion.V11)
21 |
22 | assertEquals("", escapedValue, "1.1 escapes \\u000b and \\u000c to and ")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Comment.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | /**
4 | * Similar to a [TextElement] except that the inner text is wrapped inside a comment tag ``.
5 | *
6 | * Note that `--` will be replaced with `--`.
7 | */
8 | class Comment internal constructor(val text: String) : Element {
9 | override fun render(builder: Appendable, indent: String, printOptions: PrintOptions) {
10 | val lineEnding = getLineEnding(printOptions)
11 |
12 | builder.append("$indent$lineEnding")
13 | }
14 |
15 | override fun equals(other: Any?): Boolean = other is Comment && other.text == text
16 |
17 | override fun hashCode(): Int = text.hashCode()
18 | }
19 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/nested.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Doctype.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | class Doctype @JvmOverloads constructor(
4 | private val name: String,
5 | private val systemId: String? = null,
6 | private val publicId: String? = null
7 | ) : Element {
8 |
9 | override fun render(builder: Appendable, indent: String, printOptions: PrintOptions) {
10 | builder.append("")
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/target-namespace.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `Code`(nodeName: String) : Node(nodeName) {
8 | init {
9 | xmlns = "http://code.redundent.org/schemas"
10 | }
11 |
12 | var `id`: kotlin.Long?
13 | get() = get("id")
14 | set(value) { set("id", value) }
15 | }
16 |
17 | @JvmOverloads
18 | fun `code`(`id`: kotlin.Long? = null,
19 | __block__: `Code`.() -> Unit): `Code` {
20 | val `code` = `Code`("code")
21 | `code`.apply {
22 | if (`id` != null) {
23 | this.`id` = `id`
24 | }
25 | }
26 | `code`.apply(__block__)
27 | return `code`
28 | }
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/duplicate-names.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/README.md:
--------------------------------------------------------------------------------
1 | [  ](https://bintray.com/redundent/maven/kotlin-xml-dsl-generator/_latestVersion)
2 |
3 | Kotlin XML Builder DSL Generator
4 | =
5 |
6 | This jar can be used to generate a typesafe DSL based on an XSD file.
7 |
8 | **NOTE**: This is experimental and may not generate the exact DSL you expect. Please report
9 | any issues.
10 |
11 | License
12 | -
13 | Apache 2.0
14 |
15 | Usage
16 | =
17 | Usage is similar to generating java code with XJC. Simply run:
18 |
19 | ```bash
20 | java -jar -d -p
21 | ```
22 |
23 | You can also use JAXB bindings files to customize some of the generator code. Note that this is
24 | very experimental and not guaranteed to work with all customizations.
25 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/string-to-bytearray.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `StringToByteArray`(nodeName: String) : Node(nodeName) {
8 | var `value`: kotlin.ByteArray?
9 | get() = get("value")
10 | set(value) { set("value", value) }
11 | }
12 |
13 | @JvmOverloads
14 | fun `String-To-ByteArray`(`value`: kotlin.ByteArray? = null,
15 | __block__: `StringToByteArray`.() -> Unit): `StringToByteArray` {
16 | val `String-To-ByteArray` = `StringToByteArray`("String-To-ByteArray")
17 | `String-To-ByteArray`.apply {
18 | if (`value` != null) {
19 | this.`value` = `value`
20 | }
21 | }
22 | `String-To-ByteArray`.apply(__block__)
23 | return `String-To-ByteArray`
24 | }
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/extension.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/child-prop-order.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | @XmlType(childOrder = arrayOf("Child1",
8 | "child2",
9 | "CHILD3"))
10 | open class `ChildPropOrder`(nodeName: String) : Node(nodeName)
11 |
12 | fun `ChildPropOrder`.`Child1`(value: kotlin.String) {
13 | "Child1"(value)
14 | }
15 |
16 | fun `ChildPropOrder`.`child2`(value: kotlin.String) {
17 | "child2"(value)
18 | }
19 |
20 | fun `ChildPropOrder`.`CHILD3`(value: kotlin.String) {
21 | "CHILD3"(value)
22 | }
23 |
24 | fun `ChildPropOrder`(__block__: `ChildPropOrder`.() -> Unit): `ChildPropOrder` {
25 | val `ChildPropOrder` = `ChildPropOrder`("ChildPropOrder")
26 | `ChildPropOrder`.apply(__block__)
27 | return `ChildPropOrder`
28 | }
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/member-functions.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | @XmlType(childOrder = arrayOf("comments",
8 | "sub-type"))
9 | open class `MemberType`(nodeName: String) : Node(nodeName) {
10 | var `id`: kotlin.String?
11 | get() = get("id")
12 | set(value) { set("id", value) }
13 |
14 | fun `comments`(value: kotlin.String) {
15 | "comments"(value)
16 | }
17 |
18 | fun `sub-type`(__block__: `MemberType`.`SubType`.() -> Unit): `SubType` {
19 | val `sub-type` = `SubType`()
20 | `sub-type`.apply(__block__)
21 | return `sub-type`
22 | }
23 |
24 | inner class `SubType` : Node("SubType") {
25 | fun `sub-type-element`(value: kotlin.String) {
26 | "sub-type-element"(value)
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/kotlin/org/redundent/kotlin/xml/gen/SimpleSchemaGen.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen
2 |
3 | import org.junit.Test
4 |
5 | class SimpleSchemaGen : AbstractGenTest() {
6 | @Test
7 | fun sitemap() = run()
8 |
9 | @Test
10 | fun attributes() = run()
11 |
12 | @Test
13 | fun `attributes-required`() = run()
14 |
15 | @Test
16 | fun nested() = run()
17 |
18 | @Test
19 | fun referencedType() = run()
20 |
21 | @Test
22 | fun group() = run()
23 |
24 | @Test
25 | fun extension() = run()
26 |
27 | @Test
28 | fun `string-to-bytearray`() = run(withBindingFile = true)
29 |
30 | @Test
31 | fun `target-namespace`() = run()
32 |
33 | @Test
34 | fun choice() = run()
35 |
36 | @Test
37 | fun `duplicate-names`() = run()
38 |
39 | @Test
40 | fun `child-prop-order`() = run()
41 |
42 | @Test
43 | fun `member-functions`() = run(additionalArgs = arrayOf("--use-member-functions"))
44 | }
45 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/sitemapTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import java.text.SimpleDateFormat
5 |
6 | class sitemapTest : TestBase() {
7 | @Test
8 | fun basicTest() {
9 | val urlset = urlset {
10 | for (i in 1..3) {
11 | url("http://blog.redundent.org/post/$i")
12 | }
13 | }
14 |
15 | validate(urlset)
16 | }
17 |
18 | @Test
19 | fun allElements() {
20 | val urlset = urlset {
21 | url(
22 | "http://blog.redundent.org",
23 | SimpleDateFormat("yyyy-MM-dd").parse("2017-10-24"),
24 | ChangeFreq.hourly,
25 | 14.0
26 | )
27 | }
28 | validate(urlset)
29 | }
30 |
31 | @Test
32 | fun sitemapIndex() {
33 | val format = SimpleDateFormat("yyyy-MM-dd")
34 | val sitemapIndex = sitemapindex {
35 | sitemap("http://blog.redundent.org/sitemap1.xml", format.parse("2017-10-24"))
36 | sitemap("http://blog.redundent.org/sitemap2.xml", format.parse("2016-01-01"))
37 | sitemap("http://blog.redundent.org/sitemap3.xml")
38 | }
39 |
40 | validate(sitemapIndex)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | `maven-publish`
4 | signing
5 | id("org.jlleitschuh.gradle.ktlint")
6 | }
7 |
8 | val kotlinVersion: String by rootProject.extra
9 |
10 | tasks {
11 | val jar by getting(Jar::class)
12 |
13 | register("sourceJar") {
14 | from(sourceSets["main"].allSource)
15 | destinationDirectory.set(jar.destinationDirectory)
16 | archiveClassifier.set("sources")
17 | }
18 | }
19 |
20 | dependencies {
21 | compileOnly(kotlin("stdlib", kotlinVersion))
22 | compileOnly(kotlin("reflect", kotlinVersion))
23 | implementation("org.apache.commons:commons-lang3:3.5")
24 |
25 | testImplementation("junit:junit:4.13.1")
26 | testImplementation(kotlin("reflect", kotlinVersion))
27 | testImplementation(kotlin("test-junit", kotlinVersion))
28 | }
29 |
30 | artifacts {
31 | add("archives", tasks["sourceJar"])
32 | }
33 |
34 | publishing {
35 | publications {
36 | register("maven") {
37 | from(components["java"])
38 |
39 | artifact(tasks["sourceJar"]) {
40 | classifier = "sources"
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/duplicate-names.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `DuplicateNameContainer`(nodeName: String) : Node(nodeName) {
8 | inner class `DuplicateName` : org.redundent.generated.`DuplicateName`("DuplicateName")
9 | }
10 |
11 | fun `DuplicateNameContainer`.`DuplicateName`(__block__: `DuplicateNameContainer`.`DuplicateName`.() -> Unit) {
12 | val `DuplicateName` = `DuplicateName`()
13 | `DuplicateName`.apply(__block__)
14 | this.addElement(`DuplicateName`)
15 | }
16 |
17 | open class `DuplicateNames`(nodeName: String) : org.redundent.generated.`DuplicateNameContainer`(nodeName)
18 |
19 | fun `DuplicateNames`(__block__: `DuplicateNames`.() -> Unit): `DuplicateNames` {
20 | val `DuplicateNames` = `DuplicateNames`("DuplicateNames")
21 | `DuplicateNames`.apply(__block__)
22 | return `DuplicateNames`
23 | }
24 |
25 | open class `DuplicateName`(nodeName: String) : Node(nodeName)
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/ProcessingInstructionElement.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.apache.commons.lang3.builder.HashCodeBuilder
4 |
5 | /**
6 | * Similar to a [TextElement] except that the inner text is wrapped inside `?>` tag.
7 | */
8 | class ProcessingInstructionElement internal constructor(text: String, private val attributes: Map) :
9 | TextElement(text) {
10 | override fun renderedText(printOptions: PrintOptions): String {
11 | return "$text${renderAttributes()}?>"
12 | }
13 |
14 | private fun renderAttributes(): String {
15 | if (attributes.isEmpty()) {
16 | return ""
17 | }
18 |
19 | return " " + attributes.entries.joinToString(" ") {
20 | "${it.key}=\"${it.value}\""
21 | }
22 | }
23 |
24 | override fun equals(other: Any?): Boolean {
25 | if (!super.equals(other) || other !is ProcessingInstructionElement) {
26 | return false
27 | }
28 |
29 | return attributes == other.attributes
30 | }
31 |
32 | override fun hashCode(): Int = HashCodeBuilder()
33 | .appendSuper(super.hashCode())
34 | .append(attributes)
35 | .toHashCode()
36 | }
37 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/TextElementTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertFalse
6 | import kotlin.test.assertNotEquals
7 |
8 | class TextElementTest {
9 | @Test
10 | fun testHashCode() {
11 | val text = TextElement("test")
12 |
13 | assertEquals(text.text.hashCode(), text.hashCode())
14 | }
15 |
16 | @Test
17 | fun `equals null`() {
18 | val text = TextElement("test")
19 |
20 | assertFalse(text.equals(null))
21 | }
22 |
23 | @Test
24 | fun `equals different type`() {
25 | val text = TextElement("test")
26 | val other = Comment("test")
27 |
28 | assertFalse(text.equals(other))
29 | }
30 |
31 | @Test
32 | fun `equals different text`() {
33 | val text1 = TextElement("text1")
34 | val text2 = TextElement("text2")
35 |
36 | assertNotEquals(text1, text2)
37 | assertNotEquals(text2, text1)
38 | }
39 |
40 | @Test
41 | fun equals() {
42 | val text1 = TextElement("text1")
43 | val text2 = TextElement("text1")
44 |
45 | assertEquals(text1, text2)
46 | assertEquals(text2, text1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | # Controls when the action will run. Triggers the workflow on push or pull request
6 | # events but only for the master branch
7 | on:
8 | push:
9 | branches: [ main ]
10 | pull_request:
11 | branches: [ main ]
12 | workflow_call:
13 |
14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
15 | jobs:
16 | # This workflow contains a single job called "build"
17 | build:
18 | # The type of runner that the job will run on
19 | runs-on: ubuntu-latest
20 |
21 | # Steps represent a sequence of tasks that will be executed as part of the job
22 | steps:
23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
24 | - uses: actions/checkout@v3
25 |
26 | - name: Setup Java JDK
27 | uses: actions/setup-java@v3
28 | with:
29 | distribution: temurin
30 | java-version: 8
31 |
32 | - name: Assemble
33 | run: ./gradlew assemble
34 |
35 | - name: Check
36 | run: ./gradlew check
37 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/CDATAElement.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.apache.commons.lang3.builder.HashCodeBuilder
4 |
5 | /**
6 | * Similar to a [TextElement] except that the inner text is wrapped inside a `` tag.
7 | */
8 | class CDATAElement internal constructor(text: String) : TextElement(text) {
9 | override fun renderedText(printOptions: PrintOptions): String {
10 | fun String.escapeCData(): String {
11 | val cdataEnd = "]]>"
12 | val cdataStart = "")
16 | }
17 |
18 | return ""
19 | }
20 |
21 | override fun equals(other: Any?): Boolean = super.equals(other) && other is CDATAElement
22 |
23 | // Need to use javaClass here to avoid a normal TextElement and a CDATAElement having the same hashCode if they have
24 | // the same text
25 | override fun hashCode(): Int = HashCodeBuilder()
26 | .appendSuper(super.hashCode())
27 | .append(javaClass.hashCode())
28 | .toHashCode()
29 | }
30 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/CommentTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertFalse
6 | import kotlin.test.assertNotEquals
7 |
8 | class CommentTest {
9 | @Test
10 | fun testHashCode() {
11 | val comment = Comment("test")
12 |
13 | assertEquals(comment.text.hashCode(), comment.hashCode())
14 | }
15 |
16 | @Test
17 | fun `equals null`() {
18 | val comment = Comment("test")
19 |
20 | assertFalse(comment.equals(null))
21 | }
22 |
23 | @Test
24 | fun `equals different type`() {
25 | val comment = Comment("test")
26 | val other = CDATAElement("test")
27 |
28 | assertFalse(comment.equals(other))
29 | }
30 |
31 | @Test
32 | fun `equals different text`() {
33 | val comment1 = Comment("comment1")
34 | val comment2 = Comment("comment2")
35 |
36 | assertNotEquals(comment1, comment2)
37 | assertNotEquals(comment2, comment1)
38 | }
39 |
40 | @Test
41 | fun equals() {
42 | val comment1 = Comment("comment")
43 | val comment2 = Comment("comment")
44 |
45 | assertEquals(comment1, comment2)
46 | assertEquals(comment2, comment1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/TextElement.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | /**
4 | * An element type that has some text in it.
5 | *
6 | * For example:
7 | * ```xml
8 | * http://blog.redundent.org
9 | * ```
10 | */
11 | open class TextElement internal constructor(val text: String, private val unsafe: Boolean = false) : Element {
12 | override fun render(builder: Appendable, indent: String, printOptions: PrintOptions) {
13 | if (text.isEmpty()) {
14 | return
15 | }
16 |
17 | val lineEnding = getLineEnding(printOptions)
18 |
19 | builder.append("$indent${renderedText(printOptions)}$lineEnding")
20 | }
21 |
22 | internal fun renderSingleLine(builder: Appendable, printOptions: PrintOptions) {
23 | builder.append(renderedText(printOptions))
24 | }
25 |
26 | internal open fun renderedText(printOptions: PrintOptions): String? {
27 | return if (unsafe) {
28 | text
29 | } else {
30 | escapeValue(text, printOptions.xmlVersion, printOptions.useCharacterReference)
31 | }
32 | }
33 |
34 | override fun equals(other: Any?): Boolean = other is TextElement && other.text == text
35 |
36 | override fun hashCode(): Int = text.hashCode()
37 | }
38 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/CDATAElementTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertFalse
6 | import kotlin.test.assertNotEquals
7 |
8 | class CDATAElementTest {
9 | @Test
10 | fun testHashCode() {
11 | val text = CDATAElement("test")
12 |
13 | assertNotEquals(text.text.hashCode(), text.hashCode(), "CDATA hashcode is not just text.hashCode()")
14 | }
15 |
16 | @Test
17 | fun `equals null`() {
18 | val text = CDATAElement("test")
19 |
20 | assertFalse(text.equals(null))
21 | }
22 |
23 | @Test
24 | fun `equals different type`() {
25 | val text = CDATAElement("test")
26 | val other = TextElement("test")
27 |
28 | assertNotEquals(text, other)
29 | }
30 |
31 | @Test
32 | fun `equals different text`() {
33 | val text1 = CDATAElement("text1")
34 | val text2 = CDATAElement("text2")
35 |
36 | assertNotEquals(text1, text2)
37 | assertNotEquals(text2, text1)
38 | }
39 |
40 | @Test
41 | fun equals() {
42 | val text1 = CDATAElement("text1")
43 | val text2 = CDATAElement("text1")
44 |
45 | assertEquals(text1, text2)
46 | assertEquals(text2, text1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/choice.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `MethodName`(nodeName: String) : Node(nodeName)
8 |
9 | fun `MethodName`.`type1`(__block__: `Type1`.() -> Unit) {
10 | val `type1` = `Type1`("type1")
11 | `type1`.apply(__block__)
12 | this.addElement(`type1`)
13 | }
14 |
15 | fun `MethodName`.`type2`(__block__: `Type2`.() -> Unit) {
16 | val `type2` = `Type2`("type2")
17 | `type2`.apply(__block__)
18 | this.addElement(`type2`)
19 | }
20 |
21 | fun `MethodName`.`type3`(__block__: `Type3`.() -> Unit) {
22 | val `type3` = `Type3`("type3")
23 | `type3`.apply(__block__)
24 | this.addElement(`type3`)
25 | }
26 |
27 | fun `Method-Name`(__block__: `MethodName`.() -> Unit): `MethodName` {
28 | val `Method-Name` = `MethodName`("Method-Name")
29 | `Method-Name`.apply(__block__)
30 | return `Method-Name`
31 | }
32 |
33 | open class `Type1`(nodeName: String) : Node(nodeName)
34 |
35 | open class `Type2`(nodeName: String) : Node(nodeName)
36 |
37 | open class `Type3`(nodeName: String) : Node(nodeName)
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Deploy to central
3 |
4 | on: workflow_dispatch
5 |
6 | permissions:
7 | contents: read
8 |
9 | jobs:
10 | build:
11 | uses: ./.github/workflows/ci.yml
12 | deploy:
13 | runs-on: ubuntu-latest
14 | needs: build
15 | steps:
16 | - uses: actions/checkout@v3
17 |
18 | - name: Setup Java JDK
19 | uses: actions/setup-java@v3
20 | with:
21 | distribution: temurin
22 | java-version: 8
23 |
24 | - name: Import GPG key
25 | uses: crazy-max/ghaction-import-gpg@v5
26 | with:
27 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
28 | passphrase: ${{ secrets.GPG_PRIVATE_PASSWORD }}
29 |
30 | - name: Gradle publish
31 | uses: gradle/gradle-build-action@ce999babab2de1c4b649dc15f0ee67e6246c994f
32 | with:
33 | arguments: |
34 | publish
35 | closeSonatypeStagingRepository
36 | -Psigning.gnupg.passphrase='${{secrets.GPG_PRIVATE_PASSWORD}}'
37 | -Psigning.gnupg.keyName='${{secrets.GPG_PRIVATE_KEY_ID}}'
38 | -PsonatypeUsername='${{secrets.SONATYPE_USERNAME}}'
39 | -PsonatypePassword='${{secrets.SONATYPE_PASSWORD}}'
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/SchemaOutline.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen
2 |
3 | import com.sun.tools.xjc.model.CClassInfo
4 | import com.sun.tools.xjc.model.CClassInfoParent
5 | import com.sun.tools.xjc.outline.Outline
6 | import org.redundent.kotlin.xml.gen.writer.XmlClass
7 | import org.redundent.kotlin.xml.gen.writer.XmlEnum
8 |
9 | class SchemaOutline(outline: Outline, private val opts: ExOptions) {
10 | private val innerClasses = outline.classes.filter { it.target.parent() is CClassInfo }.groupBy { it.target.parent() as CClassInfo }
11 | private val rootClasses = outline.classes.filter { it.target.parent() is CClassInfoParent.Package }
12 |
13 | val classes = rootClasses.sortedBy { if (it.target.isAbstract) 0 else 1 }.map { XmlClass(it, opts) }
14 | val enums = outline.enums.map(::XmlEnum)
15 |
16 | init {
17 | classes.forEach(this::recurseInnerClasses)
18 | }
19 |
20 | private fun recurseInnerClasses(clazz: XmlClass) {
21 | val innerClasses = innerClasses[clazz.clazz.target]
22 | innerClasses?.forEach {
23 | val innerClass = XmlClass(it, opts, innerClass = true)
24 | clazz.addInnerClass(innerClass)
25 |
26 | recurseInnerClasses(innerClass)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Utils.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.apache.commons.lang3.StringEscapeUtils
4 | import java.lang.StringBuilder
5 |
6 | internal fun escapeValue(value: Any?, xmlVersion: XmlVersion, useCharacterReference: Boolean = false): String? {
7 | val asString = value?.toString() ?: return null
8 |
9 | if (useCharacterReference) {
10 | return referenceCharacter(asString)
11 | }
12 |
13 | return when (xmlVersion) {
14 | XmlVersion.V10 -> StringEscapeUtils.escapeXml10(asString)
15 | XmlVersion.V11 -> StringEscapeUtils.escapeXml11(asString)
16 | }
17 | }
18 |
19 | internal fun referenceCharacter(asString: String): String {
20 | val builder = StringBuilder()
21 |
22 | asString.toCharArray().forEach { character ->
23 | when (character) {
24 | '\'' -> builder.append("'")
25 | '&' -> builder.append("&")
26 | '<' -> builder.append("<")
27 | '>' -> builder.append(">")
28 | '"' -> builder.append(""")
29 | else -> builder.append(character)
30 | }
31 | }
32 |
33 | return builder.toString()
34 | }
35 |
36 | internal fun buildName(name: String, namespace: Namespace?): String =
37 | if (namespace == null || namespace.isDefault) name else "${namespace.name}:$name"
38 |
39 | fun unsafe(value: Any?): Unsafe = Unsafe(value)
40 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/nested.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `Top`(nodeName: String) : Node(nodeName) {
8 | inner class `First` : Node("First") {
9 | var `Id`: kotlin.Long
10 | get() = get("Id")!!
11 | set(value) { set("Id", value) }
12 |
13 | inner class `Second` : Node("Second") {
14 | var `Name`: kotlin.String?
15 | get() = get("Name")
16 | set(value) { set("Name", value) }
17 | }
18 | }
19 |
20 | @JvmOverloads
21 | fun `First`.`second`(`Name`: kotlin.String? = null,
22 | __block__: `First`.`Second`.() -> Unit) {
23 | val `second` = `Second`()
24 | `second`.apply {
25 | if (`Name` != null) {
26 | this.`Name` = `Name`
27 | }
28 | }
29 | `second`.apply(__block__)
30 | this.addElement(`second`)
31 | }
32 | }
33 |
34 | fun `Top`.`first`(`Id`: kotlin.Long,
35 | __block__: `Top`.`First`.() -> Unit) {
36 | val `first` = `First`()
37 | `first`.apply {
38 | this.`Id` = `Id`
39 | }
40 | `first`.apply(__block__)
41 | this.addElement(`first`)
42 | }
43 |
44 | fun `top`(__block__: `Top`.() -> Unit): `Top` {
45 | val `top` = `Top`("top")
46 | `top`.apply(__block__)
47 | return `top`
48 | }
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/PrintOptions.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | class PrintOptions(
4 | /**
5 | * Whether to print newlines and tabs while rendering the document.
6 | */
7 | val pretty: Boolean = true,
8 |
9 | /**
10 | * Whether to print a single text element on the same line.
11 | *
12 | * ```
13 | *
14 | * text value
15 | *
16 | * ```
17 | *
18 | * vs
19 | *
20 | * ```
21 | * text value
22 | * ```
23 | */
24 | val singleLineTextElements: Boolean = false,
25 |
26 | /**
27 | * Whether to use "self closing" tags for empty elements.
28 | *
29 | * ```
30 | *
31 | * ```
32 | *
33 | * vs
34 | *
35 | * ```
36 | *
37 | * ```
38 | */
39 | val useSelfClosingTags: Boolean = true,
40 |
41 | /**
42 | * Whether to use escaped character or character reference
43 | *
44 | * If false: `'` becomes `'`
45 | *
46 | * If `true`: `'` becomes `'`
47 | */
48 | val useCharacterReference: Boolean = false,
49 |
50 | /**
51 | * Changes the indent for new lines when [pretty] is enabled. The option has no effect when
52 | * [pretty] is set to `false`. The default uses one tab `\t`.
53 | */
54 | val indent: String = "\t"
55 | ) {
56 | internal var xmlVersion: XmlVersion = XmlVersion.V10
57 | }
58 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2 |
3 | plugins {
4 | kotlin("jvm")
5 | id("com.github.johnrengelman.shadow") version "5.2.0"
6 | `maven-publish`
7 | signing
8 | id("org.jlleitschuh.gradle.ktlint")
9 | }
10 |
11 | val kotlinVersion: String by rootProject.extra
12 |
13 | tasks {
14 | val jar by getting(Jar::class) {
15 | manifest {
16 | attributes(mapOf("Main-Class" to "org.redundent.kotlin.xml.gen.DslGeneratorKt"))
17 | }
18 | }
19 |
20 | withType {
21 | archiveClassifier.set(null as String?)
22 | }
23 |
24 | register("sourceJar") {
25 | from(sourceSets["main"].allSource)
26 | destinationDirectory.set(jar.destinationDirectory)
27 | archiveClassifier.set("sources")
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation(kotlin("stdlib", kotlinVersion))
33 | implementation(kotlin("reflect", kotlinVersion))
34 | implementation("org.glassfish.jaxb:jaxb-xjc:2.3.8")
35 |
36 | testImplementation(project(":kotlin-xml-builder"))
37 | testImplementation("junit:junit:4.13.1")
38 | testImplementation(kotlin("test-junit", kotlinVersion))
39 | }
40 |
41 | publishing {
42 | publications {
43 | register("maven") {
44 | artifact(tasks["shadowJar"])
45 |
46 | artifact(tasks["sourceJar"]) {
47 | classifier = "sources"
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/extension.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | abstract class `BaseType`(nodeName: String) : Node(nodeName) {
8 | var `Name`: kotlin.String?
9 | get() = get("Name")
10 | set(value) { set("Name", value) }
11 | }
12 |
13 | open class `A`(nodeName: String) : org.redundent.generated.`BaseType`(nodeName) {
14 | var `Top`: kotlin.String?
15 | get() = get("Top")
16 | set(value) { set("Top", value) }
17 | }
18 |
19 | @JvmOverloads
20 | fun `a`(`Top`: kotlin.String? = null,
21 | `Name`: kotlin.String? = null,
22 | __block__: `A`.() -> Unit): `A` {
23 | val `a` = `A`("a")
24 | `a`.apply {
25 | if (`Top` != null) {
26 | this.`Top` = `Top`
27 | }
28 | if (`Name` != null) {
29 | this.`Name` = `Name`
30 | }
31 | }
32 | `a`.apply(__block__)
33 | return `a`
34 | }
35 |
36 | open class `B`(nodeName: String) : org.redundent.generated.`BaseType`(nodeName) {
37 | var `Middle`: kotlin.String?
38 | get() = get("Middle")
39 | set(value) { set("Middle", value) }
40 | }
41 |
42 | @JvmOverloads
43 | fun `b`(`Middle`: kotlin.String? = null,
44 | `Name`: kotlin.String? = null,
45 | __block__: `B`.() -> Unit): `B` {
46 | val `b` = `B`("b")
47 | `b`.apply {
48 | if (`Middle` != null) {
49 | this.`Middle` = `Middle`
50 | }
51 | if (`Name` != null) {
52 | this.`Name` = `Name`
53 | }
54 | }
55 | `b`.apply(__block__)
56 | return `b`
57 | }
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Sitemap.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import java.text.SimpleDateFormat
4 | import java.util.*
5 |
6 | const val DEFAULT_URLSET_NAMESPACE = "http://www.sitemaps.org/schemas/sitemap/0.9"
7 |
8 | class UrlSet internal constructor() : Node("urlset") {
9 | init {
10 | xmlns = DEFAULT_URLSET_NAMESPACE
11 | }
12 |
13 | fun url(
14 | loc: String,
15 | lastmod: Date? = null,
16 | changefreq: ChangeFreq? = null,
17 | priority: Double? = null
18 | ) {
19 | "url" {
20 | "loc"(loc)
21 |
22 | lastmod?.let {
23 | "lastmod"(formatDate(it))
24 | }
25 |
26 | changefreq?.let {
27 | "changefreq"(it.name)
28 | }
29 |
30 | priority?.let {
31 | "priority"(it.toString())
32 | }
33 | }
34 | }
35 | }
36 |
37 | class Sitemapindex internal constructor() : Node("sitemapindex") {
38 | init {
39 | xmlns = DEFAULT_URLSET_NAMESPACE
40 | }
41 |
42 | fun sitemap(
43 | loc: String,
44 | lastmod: Date? = null
45 | ) {
46 | "sitemap" {
47 | "loc"(loc)
48 |
49 | lastmod?.let {
50 | "lastmod"(formatDate(it))
51 | }
52 | }
53 | }
54 | }
55 |
56 | @Suppress("EnumEntryName", "ktlint:enum-entry-name-case")
57 | enum class ChangeFreq {
58 | always,
59 | hourly,
60 | daily,
61 | weekly,
62 | monthly,
63 | yearly,
64 | never
65 | }
66 |
67 | private fun formatDate(date: Date): String {
68 | return SimpleDateFormat("yyyy-MM-dd").format(date)
69 | }
70 |
71 | fun urlset(init: UrlSet.() -> Unit) = UrlSet().apply(init)
72 |
73 | fun sitemapindex(init: Sitemapindex.() -> Unit) = Sitemapindex().apply(init)
74 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/attributes.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/ProcessingInstructionElementTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertFalse
6 | import kotlin.test.assertNotEquals
7 |
8 | class ProcessingInstructionElementTest {
9 | @Test
10 | fun testHashCode() {
11 | val text = ProcessingInstructionElement("test", emptyMap())
12 |
13 | assertNotEquals(text.text.hashCode(), text.hashCode(), "ProcessingInstructionElement hashcode is not just text.hashCode()")
14 | }
15 |
16 | @Test
17 | fun `equals null`() {
18 | val text = ProcessingInstructionElement("test", emptyMap())
19 |
20 | assertFalse(text.equals(null))
21 | }
22 |
23 | @Test
24 | fun `equals different type`() {
25 | val text = ProcessingInstructionElement("test", emptyMap())
26 | val other = TextElement("test")
27 |
28 | assertNotEquals(text, other)
29 | }
30 |
31 | @Test
32 | fun `equals different text`() {
33 | val text1 = ProcessingInstructionElement("text1", emptyMap())
34 | val text2 = ProcessingInstructionElement("text2", emptyMap())
35 |
36 | assertNotEquals(text1, text2)
37 | assertNotEquals(text2, text1)
38 | }
39 |
40 | @Test
41 | fun equals() {
42 | val text1 = ProcessingInstructionElement("text1", emptyMap())
43 | val text2 = ProcessingInstructionElement("text1", emptyMap())
44 |
45 | assertEquals(text1, text2)
46 | assertEquals(text2, text1)
47 | }
48 |
49 | @Test
50 | fun `equals attributes same order`() {
51 | val text1 = ProcessingInstructionElement("text1", linkedMapOf("attr1" to "value1", "attr2" to "value2"))
52 | val text2 = ProcessingInstructionElement("text1", linkedMapOf("attr1" to "value1", "attr2" to "value2"))
53 |
54 | assertEquals(text1, text2)
55 | assertEquals(text2, text1)
56 | }
57 |
58 | @Test
59 | fun `equals attributes different order`() {
60 | val text1 = ProcessingInstructionElement("text1", linkedMapOf("attr1" to "value1", "attr2" to "value2"))
61 | val text2 = ProcessingInstructionElement("text1", linkedMapOf("attr2" to "value2", "attr1" to "value1"))
62 |
63 | assertEquals(text1, text2)
64 | assertEquals(text2, text1)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/sitemap.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks", "EnumEntryName")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | /**
8 | * OPTIONAL: Indicates how frequently the content at a particular URL is
9 | * likely to change. The value "always" should be used to describe
10 | * documents that change each time they are accessed. The value "never"
11 | * should be used to describe archived URLs. Please note that web
12 | * crawlers may not necessarily crawl pages marked "always" more often.
13 | * Consider this element as a friendly suggestion and not a command.
14 | */
15 | enum class `TChangeFreq` {
16 | `always`,
17 | `hourly`,
18 | `daily`,
19 | `weekly`,
20 | `monthly`,
21 | `yearly`,
22 | `never`
23 | }
24 |
25 | /**
26 | * Container for a set of up to 50,000 document elements.
27 | * This is the root element of the XML file.
28 | */
29 | open class `Urlset`(nodeName: String) : Node(nodeName) {
30 | init {
31 | xmlns = "http://www.sitemaps.org/schemas/sitemap/0.9"
32 | }
33 | }
34 |
35 | fun `Urlset`.`url`(__block__: `TUrl`.() -> Unit) {
36 | val `url` = `TUrl`("url")
37 | `url`.apply(__block__)
38 | this.addElement(`url`)
39 | }
40 |
41 | /**
42 | * Container for a set of up to 50,000 document elements.
43 | * This is the root element of the XML file.
44 | */
45 | fun `urlset`(__block__: `Urlset`.() -> Unit): `Urlset` {
46 | val `urlset` = `Urlset`("urlset")
47 | `urlset`.apply(__block__)
48 | return `urlset`
49 | }
50 |
51 | /**
52 | * Container for the data needed to describe a document to crawl.
53 | */
54 | @XmlType(childOrder = arrayOf("loc",
55 | "lastmod",
56 | "changefreq",
57 | "priority"))
58 | open class `TUrl`(nodeName: String) : Node(nodeName)
59 |
60 | fun `TUrl`.`loc`(value: kotlin.String) {
61 | "loc"(value)
62 | }
63 |
64 | fun `TUrl`.`lastmod`(value: kotlin.String) {
65 | "lastmod"(value)
66 | }
67 |
68 | fun `TUrl`.`changefreq`(value: org.redundent.generated.TChangeFreq) {
69 | "changefreq"(value.toString())
70 | }
71 |
72 | fun `TUrl`.`priority`(value: java.math.BigDecimal) {
73 | "priority"(value.toString())
74 | }
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/kotlin/org/redundent/kotlin/xml/gen/AbstractGenTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen
2 |
3 | import org.junit.Rule
4 | import org.junit.rules.TestName
5 | import java.io.File
6 | import java.io.FileNotFoundException
7 | import java.io.InputStream
8 | import java.io.InputStreamReader
9 | import kotlin.test.assertEquals
10 |
11 | abstract class AbstractGenTest {
12 | @Suppress("MemberVisibilityCanBePrivate")
13 | @get:Rule
14 | val testName = TestName()
15 |
16 | protected fun run(withBindingFile: Boolean = false, vararg additionalArgs: String) {
17 | val schema = javaClass.getResourceAsStream("/schema/${testName.methodName}.xsd")
18 | ?: throw FileNotFoundException("/schema/${testName.methodName}.xsd as not found")
19 | val code = getExpectedClassText()
20 |
21 | val file = File.createTempFile("schema", ".xsd").apply {
22 | outputStream().use {
23 | schema.copyTo(it)
24 | }
25 | }
26 |
27 | val opts = ExOptions().apply { parseArguments(arrayOf("-p", "org.redundent.generated", file.absolutePath) + additionalArgs) }
28 |
29 | if (withBindingFile) {
30 | val binding = javaClass.getResourceAsStream("/schema/${testName.methodName}.jxb")
31 | ?: throw FileNotFoundException("/schema/${testName.methodName}.jxb as not found")
32 | val bindingFile = File.createTempFile("binding", ".jxb").apply {
33 | writer().use { writer ->
34 | binding.reader().forEachLine { line ->
35 | writer.appendLine(line.replace("@schema@", "file:/${file.absolutePath}"))
36 | }
37 | }
38 | }
39 |
40 | opts.addBindFile(bindingFile)
41 | }
42 |
43 | val text = cleanText(DslGenerator(opts).generate().replace(System.lineSeparator(), "\n"))
44 |
45 | assertEquals(code, text, "generated code is not the same")
46 | }
47 |
48 | private fun getExpectedClassText(): String {
49 | val inputStream = getInputStream()
50 | inputStream.use {
51 | return InputStreamReader(it).readText().replace(System.lineSeparator(), "\n")
52 | }
53 | }
54 |
55 | private fun getInputStream(): InputStream {
56 | val resName = "/code/${testName.methodName}.kt"
57 | return javaClass.getResourceAsStream(resName)
58 | ?: throw FileNotFoundException("$resName as not found")
59 | }
60 |
61 | private fun cleanText(text: String): String {
62 | return text.split("\n").joinToString("\n", transform = String::trimEnd)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/writer/XmlElement.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen.writer
2 |
3 | import com.sun.tools.xjc.model.CClassInfo
4 | import com.sun.tools.xjc.model.CNonElement
5 |
6 | class XmlElement(
7 | val name: String,
8 | val type: CNonElement,
9 | private val tagName: String,
10 | private val documentation: String?,
11 | private val parent: CClassInfo? = null
12 | ) : Code {
13 |
14 | override fun write(codeWriter: CodeWriter) {
15 | val rootElement = parent == null
16 | with(codeWriter) {
17 | writeKotlinDoc(documentation)
18 |
19 | val funLine = "fun ${if (!rootElement) "`${parent!!.shortName}`." else ""}`$name`"
20 |
21 | if (type is CClassInfo && type.hasOptionalAttributes) {
22 | writeln("@${JvmOverloads::class.simpleName}")
23 | }
24 |
25 | write(funLine)
26 |
27 | if (type is CClassInfo) {
28 | val blockParamType =
29 | "${((type.parent() as? CClassInfo)?.let { "`${it.shortName}`." } ?: "")}`${type.shortName}`"
30 |
31 | writeln(
32 | "(${type.attributesAsParameters(funLine.length + (currentIndex * 4) + 1)}__block__: $blockParamType.() -> Unit)${if (rootElement) ": `${type.shortName}`" else ""} {",
33 | false
34 | )
35 | indent()
36 | writeln("val `$tagName` = `${type.shortName}`(${if (type.parent() is CClassInfo) "" else "\"$tagName\""})")
37 | if (type.allAttributes.isNotEmpty()) {
38 | writeln("`$tagName`.apply {")
39 | indent()
40 | for (attr in type.allAttributes) {
41 | val attrName = attr.xmlName.localPart
42 | if (!attr.isRequired) {
43 | writeln("if (`$attrName` != null) {")
44 | writeln("\tthis.`$attrName` = `$attrName`")
45 | writeln("}")
46 | } else {
47 | writeln("this.`$attrName` = `$attrName`")
48 | }
49 | }
50 | dedent()
51 | writeln("}")
52 | }
53 |
54 | writeln("`$tagName`.apply(__block__)")
55 | if (rootElement) {
56 | writeln("return `$tagName`")
57 | } else {
58 | writeln("this.addElement(`$tagName`)")
59 | }
60 |
61 | dedent()
62 | writeln("}\n")
63 | } else {
64 | val t = CodeWriter.mapType(type.type.fullName())
65 | writeln("(value: $t) {", false)
66 | indent()
67 | writeln("\"$tagName\"(value${if (t != String::class.qualifiedName) ".toString()" else ""})")
68 | dedent()
69 | writeln("}\n")
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/attributes-required.xsd:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/TestBase.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Rule
4 | import org.junit.rules.TestName
5 | import org.w3c.dom.Document
6 | import java.io.InputStream
7 | import java.io.InputStreamReader
8 | import java.io.StringWriter
9 | import java.util.MissingResourceException
10 | import javax.xml.parsers.DocumentBuilderFactory
11 | import javax.xml.transform.OutputKeys
12 | import javax.xml.transform.TransformerFactory
13 | import javax.xml.transform.dom.DOMSource
14 | import javax.xml.transform.stream.StreamResult
15 | import kotlin.test.assertEquals
16 |
17 | open class TestBase {
18 | @get:Rule
19 | val testName = TestName()
20 |
21 | private fun getExpectedXml(): String {
22 | val inputStream = getInputStream()
23 | inputStream.use {
24 | return InputStreamReader(it).readText().replace(System.lineSeparator(), "\n")
25 | }
26 | }
27 |
28 | protected fun getInputStream(): InputStream {
29 | val resName = "/test-results/${javaClass.simpleName}/${testName.methodName}.xml"
30 | return javaClass.getResourceAsStream(resName)
31 | ?: throw MissingResourceException("Cannot find expected xml resource: $resName. Did you forget to create it?", javaClass.name, testName.methodName)
32 | }
33 |
34 | protected fun validate(xml: Node, prettyFormat: Boolean = true) {
35 | validate(xml, PrintOptions(pretty = prettyFormat))
36 | }
37 |
38 | protected fun validate(xml: Node, printOptions: PrintOptions) {
39 | val actual = xml.toString(printOptions)
40 |
41 | // Doing a replace to cater for different line endings.
42 | assertEquals(getExpectedXml(), actual.replace(System.lineSeparator(), "\n"), "actual xml matches what is expected")
43 |
44 | validateXml(actual)
45 | }
46 |
47 | protected fun validateXml(actual: String): Document {
48 | return actual.byteInputStream().use {
49 | DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(it)
50 | }
51 | }
52 |
53 | protected fun validateTest(xml: Node) {
54 | val actual = validateXml(xml.toString())
55 | val expected = getInputStream().use {
56 | DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(it)
57 | }
58 |
59 | val actualString = actual.transform()
60 | val expectedString = expected.transform()
61 |
62 | assertEquals(expectedString, actualString, "actual xml matches what is expected")
63 | }
64 |
65 | private fun Document.transform(): String {
66 | val sw = StringWriter()
67 | val tf = TransformerFactory.newInstance()
68 | val transformer = tf.newTransformer()
69 | transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no")
70 | transformer.setOutputProperty(OutputKeys.METHOD, "xml")
71 | transformer.setOutputProperty(OutputKeys.INDENT, "yes")
72 | transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8")
73 |
74 | transformer.transform(DOMSource(this), StreamResult(sw))
75 | return sw.toString()
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/DslGenerator.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen
2 |
3 | import com.sun.codemodel.JCodeModel
4 | import com.sun.tools.xjc.BadCommandLineException
5 | import com.sun.tools.xjc.ErrorReceiver
6 | import com.sun.tools.xjc.ModelLoader
7 | import com.sun.tools.xjc.Options
8 | import com.sun.tools.xjc.generator.bean.BeanGenerator
9 | import org.redundent.kotlin.xml.gen.writer.CodeWriter
10 | import org.xml.sax.SAXParseException
11 | import java.io.File
12 | import kotlin.system.exitProcess
13 |
14 | fun main(args: Array) {
15 | try {
16 | val opts = ExOptions().apply { parseArguments(args) }
17 | val output =
18 | File(opts.targetDir, "${opts.defaultPackage.replace(".", File.separator)}${File.separator}schema.kt")
19 |
20 | println("\nGenerating schema to ${output.absolutePath}")
21 |
22 | val generated = DslGenerator(opts).generate()
23 |
24 | output.parentFile.mkdirs()
25 |
26 | output.writer().use {
27 | it.append(generated)
28 | }
29 | } catch (e: BadCommandLineException) {
30 | // there was an error in the command line.
31 | // print usage and abort.
32 | if (e.message != null) {
33 | println("\n${e.message}")
34 | println()
35 | }
36 |
37 | usage()
38 | exitProcess(-1)
39 | }
40 | }
41 |
42 | fun usage() {
43 | println(
44 | """Usage: java -jar kotlin-xml-dsl-generator.jar [-options ...] ... [-b ] ...
45 |
46 | Options:
47 | -b : specify external JAXB bindings files
48 | This is experimental
49 | -d : generated files will go into this directory
50 | -p : specifies the target package"""
51 | )
52 | }
53 |
54 | class DslGenerator(private val opts: ExOptions) {
55 | fun generate(): String {
56 | val model = ModelLoader.load(opts, JCodeModel(), ErrReceiver())
57 | ?: throw BadCommandLineException("Something failed generating the code model")
58 |
59 | val outline = BeanGenerator.generate(model, ErrReceiver())
60 |
61 | val codeWriter = CodeWriter(outline)
62 | val schemaOutline = SchemaOutline(outline, opts)
63 |
64 | codeWriter.writeSuppress {
65 | addAll(
66 | listOf(
67 | "PropertyName",
68 | "ReplaceArrayOfWithLiteral",
69 | "LocalVariableName",
70 | "FunctionName",
71 | "RemoveRedundantBackticks"
72 | )
73 | )
74 |
75 | if (schemaOutline.enums.isNotEmpty()) {
76 | add("EnumEntryName")
77 | }
78 | }
79 |
80 | codeWriter.writePackage(opts.defaultPackage)
81 | codeWriter.writeImport("org.redundent.kotlin.xml.*\n")
82 |
83 | schemaOutline.enums.forEach { it.write(codeWriter) }
84 |
85 | schemaOutline.classes.forEach { it.write(codeWriter) }
86 |
87 | return codeWriter.asText()
88 | }
89 | }
90 |
91 | class ErrReceiver : ErrorReceiver() {
92 | override fun warning(exception: SAXParseException) {
93 | println(exception)
94 | }
95 |
96 | override fun info(exception: SAXParseException) {
97 | println(exception)
98 | }
99 |
100 | override fun error(exception: SAXParseException) = throw exception
101 | override fun fatalError(exception: SAXParseException) = throw exception
102 | }
103 |
104 | class ExOptions : Options() {
105 | var useMemberFunctions = false
106 |
107 | override fun parseArgument(args: Array, i: Int): Int {
108 | if (args[i] == "--use-member-functions") {
109 | useMemberFunctions = true
110 | return 1
111 | }
112 |
113 | return super.parseArgument(args, i)
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/writer/XmlClass.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen.writer
2 |
3 | import com.sun.tools.xjc.outline.ClassOutline
4 | import org.redundent.kotlin.xml.gen.ExOptions
5 |
6 | class XmlClass(
7 | val clazz: ClassOutline,
8 | private val opts: ExOptions,
9 | innerClass: Boolean = false
10 | ) : Code {
11 | val name: String = clazz.target.shortName
12 | val xmlns: String? =
13 | if (clazz.target.isElement && clazz.target.elementName.namespaceURI.isNotBlank()) clazz.target.elementName.namespaceURI else null
14 |
15 | private val superClassName: String =
16 | clazz.superClass?.target?.let { "${it.ownerPackage.name()}.`${it.shortName}`" } ?: "Node"
17 | private val abstract: Boolean = clazz.target.isAbstract
18 | private val attributes: MutableList = ArrayList()
19 | private val memberElements: MutableList = ArrayList()
20 | private var rootElement: XmlElement? = null
21 | private val innerClasses: MutableList = ArrayList()
22 |
23 | private val modifier = when {
24 | abstract -> "abstract"
25 | innerClass -> "inner"
26 | else -> "open"
27 | }
28 |
29 | private val constructorArg = if (!innerClass) "(nodeName: String)" else ""
30 | private val superClassConstructorArg = if (innerClass) "\"$name\"" else "nodeName"
31 |
32 | private val hasBody: Boolean
33 | get() = attributes.isNotEmpty() || xmlns != null || innerClasses.isNotEmpty() || (opts.useMemberFunctions && memberElements.isNotEmpty())
34 |
35 | init {
36 | clazz.target.attributes.map {
37 | val field = clazz.parent().getField(it)
38 | val name = it.xmlName.localPart
39 | val type = "${CodeWriter.mapType(field.rawType.fullName())}${if (!it.isRequired) "?" else ""}"
40 |
41 | XmlAttribute(name, type, it.isRequired)
42 | }.forEach { attributes.add(it) }
43 |
44 | clazz.target.elements.forEach { element ->
45 | element.types.forEach { type ->
46 | memberElements.add(
47 | XmlElement(
48 | type.tagName.localPart,
49 | type.target,
50 | type.tagName.localPart,
51 | element.documentation,
52 | if (opts.useMemberFunctions) null else clazz.target
53 | )
54 | )
55 | }
56 | }
57 |
58 | if (clazz.target.isElement) {
59 | val name = clazz.target.elementName.localPart
60 | rootElement = XmlElement(name, clazz.target, name, clazz.target.documentation)
61 | }
62 | }
63 |
64 | override fun write(codeWriter: CodeWriter) {
65 | with(codeWriter) {
66 | writeKotlinDoc(clazz.target.documentation)
67 |
68 | if (clazz.target.isOrdered && clazz.target.elements.size > 1) {
69 | writeln("@XmlType(childOrder = arrayOf(${clazz.target.elements.joinToString(",\n\t\t") { "\"${it.types.first().tagName.localPart}\"" }}))")
70 | }
71 | writeln("$modifier class `$name`$constructorArg : $superClassName($superClassConstructorArg)${if (hasBody) " {" else "\n"}")
72 | indent()
73 |
74 | if (xmlns != null) {
75 | writeBlock {
76 | """
77 | init {
78 | xmlns = "$xmlns"
79 | }
80 | """
81 | }
82 | }
83 |
84 | if (attributes.isNotEmpty()) {
85 | attributes.forEach { it.write(this) }
86 | writeln()
87 | }
88 |
89 | if (opts.useMemberFunctions) {
90 | if (memberElements.isNotEmpty()) {
91 | memberElements.forEach { it.write(this) }
92 | }
93 | }
94 |
95 | if (innerClasses.isNotEmpty()) {
96 | innerClasses.forEach { it.write(this) }
97 | }
98 |
99 | if (hasBody) {
100 | trimLastNewLine()
101 | }
102 |
103 | dedent()
104 |
105 | if (hasBody) {
106 | writeln("}\n")
107 | }
108 |
109 | if (!opts.useMemberFunctions) {
110 | memberElements.forEach { it.write(this) }
111 | }
112 |
113 | rootElement?.write(this)
114 | }
115 | }
116 |
117 | fun addInnerClass(xmlClass: XmlClass) {
118 | innerClasses.add(xmlClass)
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/XmlBuilder.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.w3c.dom.Document
4 | import org.xml.sax.InputSource
5 | import java.io.File
6 | import java.io.InputStream
7 | import javax.xml.parsers.DocumentBuilderFactory
8 | import kotlin.math.min
9 | import org.w3c.dom.Node as W3CNode
10 |
11 | internal fun getLineEnding(printOptions: PrintOptions) = if (printOptions.pretty) System.lineSeparator() else ""
12 |
13 | /**
14 | * Creates a new XML document with the specified root element name.
15 | *
16 | * @param root The root element name
17 | * @param encoding The encoding to use for the XML prolog
18 | * @param version The XML specification version to use for the xml prolog and attribute encoding
19 | * @param namespace Optional namespace object to use to build the name of the attribute. This will also add an `xmlns`
20 | * attribute for this value
21 | * @param init The block that defines the content of the XML
22 | */
23 | fun xml(
24 | root: String,
25 | encoding: String? = null,
26 | version: XmlVersion? = null,
27 | namespace: Namespace? = null,
28 | init: (Node.() -> Unit)? = null
29 | ): Node {
30 | val node = Node(buildName(root, namespace))
31 | if (encoding != null) {
32 | node.encoding = encoding
33 | }
34 |
35 | if (version != null) {
36 | node.version = version
37 | }
38 |
39 | if (init != null) {
40 | node.init()
41 | }
42 |
43 | if (namespace != null) {
44 | node.namespace(namespace)
45 | }
46 | return node
47 | }
48 |
49 | /**
50 | * Creates a new XML document with the specified root element name.
51 | *
52 | * @param name The name of the element
53 | * @param init The block that defines the content of the XML
54 | */
55 | fun node(name: String, namespace: Namespace? = null, init: (Node.() -> Unit)? = null): Node {
56 | val node = Node(buildName(name, namespace))
57 | if (init != null) {
58 | node.init()
59 | }
60 | return node
61 | }
62 |
63 | fun parse(f: File): Node = parse(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f))
64 | fun parse(uri: String): Node = parse(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(uri))
65 | fun parse(inputSource: InputSource): Node =
66 | parse(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource))
67 |
68 | fun parse(inputStream: InputStream): Node =
69 | parse(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream))
70 |
71 | fun parse(inputStream: InputStream, systemId: String): Node =
72 | parse(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream, systemId))
73 |
74 | fun parse(document: Document): Node {
75 | val root = document.documentElement
76 |
77 | val result = xml(root.tagName)
78 |
79 | copyAttributes(root, result)
80 |
81 | val children = root.childNodes
82 | (0 until children.length)
83 | .map(children::item)
84 | .forEach { copy(it, result) }
85 |
86 | return result
87 | }
88 |
89 | private fun copy(source: W3CNode, dest: Node) {
90 | when (source.nodeType) {
91 | W3CNode.ELEMENT_NODE -> {
92 | val cur = dest.element(source.nodeName)
93 |
94 | copyAttributes(source, cur)
95 |
96 | val children = source.childNodes
97 | (0 until children.length)
98 | .map(children::item)
99 | .forEach { copy(it, cur) }
100 | }
101 |
102 | W3CNode.CDATA_SECTION_NODE -> {
103 | dest.cdata(source.nodeValue)
104 | }
105 |
106 | W3CNode.TEXT_NODE -> {
107 | dest.text(source.nodeValue.trim { it.isWhitespace() || it == '\r' || it == '\n' })
108 | }
109 | }
110 | }
111 |
112 | private fun copyAttributes(source: W3CNode, dest: Node) {
113 | val attributes = source.attributes
114 | if (attributes == null || attributes.length == 0) {
115 | return
116 | }
117 |
118 | (0 until attributes.length)
119 | .map(attributes::item)
120 | .forEach {
121 | if (it.nodeName.startsWith("xmlns")) {
122 | dest.namespace(it.nodeName.substring(min(6, it.nodeName.length)), it.nodeValue)
123 | } else {
124 | dest.attribute(it.nodeName, it.nodeValue)
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/schema/sitemap.xsd:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | XML Schema for Sitemap files.
9 | Last Modifed 2008-03-26
10 |
11 |
12 |
13 |
14 |
15 |
16 | Container for a set of up to 50,000 document elements.
17 | This is the root element of the XML file.
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Container for the data needed to describe a document to crawl.
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | REQUIRED: The location URI of a document.
47 | The URI must conform to RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt).
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | OPTIONAL: The date the document was last modified. The date must conform
60 | to the W3C DATETIME format (http://www.w3.org/TR/NOTE-datetime).
61 | Example: 2005-05-10
62 | Lastmod may also contain a timestamp.
63 | Example: 2005-05-10T17:33:30+08:00
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | OPTIONAL: Indicates how frequently the content at a particular URL is
80 | likely to change. The value "always" should be used to describe
81 | documents that change each time they are accessed. The value "never"
82 | should be used to describe archived URLs. Please note that web
83 | crawlers may not necessarily crawl pages marked "always" more often.
84 | Consider this element as a friendly suggestion and not a command.
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | OPTIONAL: The priority of a particular URL relative to other pages
102 | on the same site. The value for this element is a number between
103 | 0.0 and 1.0 where 0.0 identifies the lowest priority page(s).
104 | The default priority of a page is 0.5. Priority is used to select
105 | between pages on your site. Setting a priority of 1.0 for all URLs
106 | will not help you, as the relative priority of pages on your site
107 | is what will be considered.
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/main/kotlin/org/redundent/kotlin/xml/gen/writer/CodeWriter.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml.gen.writer
2 |
3 | import com.sun.tools.xjc.model.CAttributePropertyInfo
4 | import com.sun.tools.xjc.model.CClassInfo
5 | import com.sun.tools.xjc.model.CElementPropertyInfo
6 | import com.sun.tools.xjc.model.CPropertyInfo
7 | import com.sun.tools.xjc.model.CTypeInfo
8 | import com.sun.tools.xjc.outline.Outline
9 | import com.sun.tools.xjc.reader.xmlschema.bindinfo.BindInfo
10 | import com.sun.xml.xsom.XSComponent
11 | import java.util.Date
12 | import kotlin.math.floor
13 |
14 | class CodeWriter(private val outline: Outline) {
15 | private var output = StringBuilder()
16 |
17 | var currentIndex = 0
18 | private set
19 |
20 | fun writePackage(packageName: String) {
21 | writeln("package $packageName\n")
22 | }
23 |
24 | fun writeImport(importStatement: String) {
25 | writeln("import $importStatement")
26 | }
27 |
28 | fun writeKotlinDoc(doc: String?) {
29 | if (doc.isNullOrBlank()) {
30 | return
31 | }
32 |
33 | writeln("/**")
34 | doc.split("\n").map(String::trim).filter(String::isNotBlank).forEach { writeln(" * $it") }
35 | writeln(" */")
36 | }
37 |
38 | fun indent() = currentIndex++
39 |
40 | fun dedent() = currentIndex--
41 |
42 | fun write(text: String, writeIndex: Boolean = true) {
43 | if (writeIndex) {
44 | output.append((0 until currentIndex).joinToString("") { "\t" })
45 | }
46 | output.append(text)
47 | }
48 |
49 | fun writeSuppress(block: MutableList.() -> Unit) {
50 | val list = ArrayList()
51 | list.block()
52 |
53 | writeln("@file:Suppress(${list.joinToString(", ") { "\"$it\"" }})")
54 | writeln()
55 | }
56 |
57 | fun writeln(text: String = "", writeIndex: Boolean = true) {
58 | if (text.isNotEmpty()) {
59 | write(text, writeIndex)
60 | }
61 | output.append("\n")
62 | }
63 |
64 | fun writeBlock(block: () -> String) {
65 | val text = block().trimStart()
66 | text.split("\n").forEach { writeln(it) }
67 | }
68 |
69 | fun asText(): String = output.toString().trim()
70 |
71 | fun trimLastNewLine() {
72 | if (output.endsWith('\n')) {
73 | output = StringBuilder(output.substring(0, output.lastIndex))
74 | }
75 | }
76 |
77 | fun CClassInfo.attributesAsParameters(indentLength: Int): String {
78 | if (allAttributes.isEmpty()) {
79 | return ""
80 | }
81 |
82 | val sortedAttributes = allAttributes.sortedWith { o1, o2 -> (if (o1.isRequired) 0 else 1).compareTo(if (o2.isRequired) 0 else 1) }
83 |
84 | val numOfTabs = floor(indentLength.toDouble() / 4.0).toInt()
85 | val numOfSpaces = indentLength % 4
86 |
87 | val delimiter = ",\n${(0..numOfTabs).joinToString("\t") { "" }}${(0..numOfSpaces).joinToString(" ") { "" }}"
88 |
89 | return sortedAttributes.joinToString(delimiter) {
90 | val field = outline.getField(it)
91 | "`${it.xmlName.localPart}`: ${mapType(field.rawType.fullName())}${if (!it.isRequired) "? = null" else ""}"
92 | } + delimiter
93 | }
94 |
95 | companion object {
96 | fun mapType(type: String): String {
97 | return when (type) {
98 | "int",
99 | java.lang.Integer::class.java.name,
100 | java.math.BigInteger::class.java.name -> Int::class.qualifiedName
101 | "long",
102 | java.lang.Long::class.java.name -> Long::class.qualifiedName
103 | "boolean",
104 | java.lang.Boolean::class.java.name -> Boolean::class.qualifiedName
105 | "double",
106 | java.lang.Double::class.java.name -> Double::class.qualifiedName
107 | "float",
108 | java.lang.Float::class.java.name -> Float::class.qualifiedName
109 | java.lang.String::class.java.name,
110 | javax.xml.namespace.QName::class.java.name -> String::class.qualifiedName
111 | "byte",
112 | java.lang.Byte::class.java.name -> Byte::class.qualifiedName
113 | "short",
114 | java.lang.Short::class.java.name -> Short::class.qualifiedName
115 | "byte[]" -> ByteArray::class.qualifiedName
116 | javax.xml.datatype.XMLGregorianCalendar::class.java.name -> Date::class.qualifiedName
117 | else -> type
118 | }!!
119 | }
120 | }
121 | }
122 |
123 | val CClassInfo.attributes: List
124 | get() = properties.mapNotNull { it as? CAttributePropertyInfo }
125 | val CClassInfo.allAttributes: List
126 | get() = generateSequence(this) { it.baseClass }.toList().flatMap { it.properties.mapNotNull { p -> p as? CAttributePropertyInfo } }
127 | val CClassInfo.hasOptionalAttributes: Boolean
128 | get() = allAttributes.any { !it.isRequired }
129 | val CClassInfo.elements: List
130 | get() = properties.mapNotNull { it as? CElementPropertyInfo }
131 | val CPropertyInfo.documentation: String?
132 | get() = schemaComponent?.documentation
133 | val CTypeInfo.documentation: String?
134 | get() = schemaComponent?.documentation
135 | val XSComponent.documentation: String?
136 | get() = annotation?.annotation?.let { it as? BindInfo }?.documentation
137 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/NodeTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertFalse
6 | import kotlin.test.assertNotEquals
7 | import kotlin.test.assertSame
8 |
9 | class NodeTest : TestBase() {
10 | @Test
11 | fun `equals null`() {
12 | val xml = xml("test")
13 |
14 | assertFalse(xml.equals(null))
15 | }
16 |
17 | @Test
18 | fun `equals different type`() {
19 | val xml = xml("test")
20 | val other = TextElement("test")
21 |
22 | assertFalse(xml == other)
23 | }
24 |
25 | @Test
26 | fun `equals different name`() {
27 | val xml1 = xml("test1")
28 | val xml2 = xml("test2")
29 |
30 | assertNotEquals(xml1, xml2)
31 | assertNotEquals(xml2, xml1)
32 | }
33 |
34 | @Test
35 | fun equals() {
36 | val xml1 = xml("complex_node", encoding = "utf-8", version = XmlVersion.V11) {
37 | xmlns = "https://test.com"
38 | namespace("t", "https://t.co")
39 |
40 | globalProcessingInstruction("global_pi", "global" to "top_level")
41 |
42 | attribute("attr", "value")
43 | attribute("other_attr", "some text & more")
44 |
45 | processingInstruction("blah", "pi_attr" to "value")
46 |
47 | "child1"("text")
48 |
49 | "child2" {
50 | comment("comment1")
51 | }
52 | }
53 |
54 | val xml2 = xml("complex_node", encoding = "utf-8", version = XmlVersion.V11) {
55 | xmlns = "https://test.com"
56 | namespace("t", "https://t.co")
57 |
58 | globalProcessingInstruction("global_pi", "global" to "top_level")
59 |
60 | attribute("attr", "value")
61 | attribute("other_attr", "some text & more")
62 |
63 | processingInstruction("blah", "pi_attr" to "value")
64 |
65 | "child1"("text")
66 |
67 | "child2" {
68 | comment("comment1")
69 | }
70 | }
71 |
72 | assertEquals(xml1, xml2)
73 | assertEquals(xml2, xml1)
74 | }
75 |
76 | @Test
77 | fun `equals slight difference`() {
78 | val xml1 = xml("complex_node", encoding = "utf-8", version = XmlVersion.V11) {
79 | xmlns = "https://test.com"
80 | namespace("t", "https://t.co")
81 |
82 | globalProcessingInstruction("global_pi", "global" to "top_level")
83 |
84 | attribute("attr", "value")
85 | attribute("other_attr", "some text & more")
86 |
87 | processingInstruction("blah", "pi_attr" to "value")
88 |
89 | "child1"("text")
90 |
91 | "child2" {
92 | comment("comment1")
93 | }
94 | }
95 |
96 | val xml2 = xml("complex_node", encoding = "utf-8", version = XmlVersion.V11) {
97 | xmlns = "https://test.com"
98 | namespace("t", "https://t.co")
99 |
100 | globalProcessingInstruction("global_pi", "global" to "top_level")
101 |
102 | attribute("attr", "value")
103 | attribute("other_attr", "some text & more")
104 |
105 | processingInstruction("blah", "pi_attr" to "value")
106 |
107 | "child1"("text")
108 |
109 | "child2" {
110 | comment("comment2")
111 | }
112 | }
113 |
114 | assertNotEquals(xml1, xml2)
115 | assertNotEquals(xml2, xml1)
116 | }
117 |
118 | @Suppress("ReplaceGetOrSet")
119 | @Test
120 | fun set() {
121 | val xml = xml("root")
122 |
123 | xml.set("myAttr", "myValue")
124 |
125 | assertEquals("myValue" as String?, xml.get("myAttr"))
126 | }
127 |
128 | @Suppress("ReplaceGetOrSet")
129 | @Test
130 | fun `set null`() {
131 | val xml = xml("root")
132 |
133 | xml.set("myAttr", "myValue")
134 | assertEquals("myValue" as String?, xml.get("myAttr"))
135 |
136 | xml.set("myAttr", null)
137 | assertFalse(xml.hasAttribute("myAttr"))
138 | }
139 |
140 | @Test
141 | fun `addElements varargs`() {
142 | val xml = xml("root")
143 |
144 | val text = TextElement("test")
145 | val cdata = CDATAElement("cdata")
146 |
147 | xml.addElements(text, cdata)
148 |
149 | assertSame(text, xml.children[0], "first child is text element")
150 | assertSame(cdata, xml.children[1], "second child is cdata element")
151 | }
152 |
153 | @Test
154 | fun `addElements iterable`() {
155 | val xml = xml("root")
156 |
157 | val text = TextElement("test")
158 | val cdata = CDATAElement("cdata")
159 |
160 | xml.addElements(listOf(text, cdata))
161 |
162 | assertSame(text, xml.children[0], "first child is text element")
163 | assertSame(cdata, xml.children[1], "second child is cdata element")
164 | }
165 |
166 | @Test(expected = IllegalArgumentException::class)
167 | fun `addElementsAfter not found`() {
168 | val xml = xml("root")
169 | val text = TextElement("test")
170 | xml.addElements(text)
171 |
172 | val after = CDATAElement("cdata")
173 |
174 | xml.addElementsAfter(after, TextElement("new"))
175 | }
176 |
177 | @Test
178 | fun addElementsAfter() {
179 | val after = node("third")
180 |
181 | val xml = xml("root") {
182 | "first"("")
183 | "second"("")
184 | addElement(after)
185 | "fourth"("")
186 | "fifth"("")
187 | }
188 |
189 | xml.addElementsAfter(
190 | after,
191 | node("new1"),
192 | node("new2")
193 | )
194 |
195 | validate(
196 | xml,
197 | PrintOptions(
198 | singleLineTextElements = true,
199 | useSelfClosingTags = true
200 | )
201 | )
202 | }
203 |
204 | @Test(expected = IllegalArgumentException::class)
205 | fun `addElementsBefore not found`() {
206 | val xml = xml("root")
207 | val text = TextElement("test")
208 | xml.addElements(text)
209 |
210 | val before = CDATAElement("cdata")
211 |
212 | xml.addElementsBefore(before, TextElement("new"))
213 | }
214 |
215 | @Test
216 | fun addElementsBefore() {
217 | val before = node("third")
218 |
219 | val xml = xml("root") {
220 | "first"("")
221 | "second"("")
222 | addElement(before)
223 | "fourth"("")
224 | "fifth"("")
225 | }
226 |
227 | xml.addElementsBefore(
228 | before,
229 | node("new1"),
230 | node("new2")
231 | )
232 |
233 | validate(
234 | xml,
235 | PrintOptions(
236 | singleLineTextElements = true,
237 | useSelfClosingTags = true
238 | )
239 | )
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/attributes-required.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `AttributesRequired`(nodeName: String) : Node(nodeName) {
8 | var `uri`: kotlin.String
9 | get() = get("uri")!!
10 | set(value) { set("uri", value) }
11 | var `base64Binary`: kotlin.ByteArray
12 | get() = get("base64Binary")!!
13 | set(value) { set("base64Binary", value) }
14 | var `boolean`: kotlin.Boolean
15 | get() = get("boolean")!!
16 | set(value) { set("boolean", value) }
17 | var `byte`: kotlin.Byte
18 | get() = get("byte")!!
19 | set(value) { set("byte", value) }
20 | var `date`: java.util.Date
21 | get() = get("date")!!
22 | set(value) { set("date", value) }
23 | var `dateTime`: java.util.Date
24 | get() = get("dateTime")!!
25 | set(value) { set("dateTime", value) }
26 | var `decimal`: java.math.BigDecimal
27 | get() = get("decimal")!!
28 | set(value) { set("decimal", value) }
29 | var `double`: kotlin.Double
30 | get() = get("double")!!
31 | set(value) { set("double", value) }
32 | var `duration`: javax.xml.datatype.Duration
33 | get() = get("duration")!!
34 | set(value) { set("duration", value) }
35 | var `float`: kotlin.Float
36 | get() = get("float")!!
37 | set(value) { set("float", value) }
38 | var `gDay`: java.util.Date
39 | get() = get("gDay")!!
40 | set(value) { set("gDay", value) }
41 | var `gMonth`: java.util.Date
42 | get() = get("gMonth")!!
43 | set(value) { set("gMonth", value) }
44 | var `gMonthDay`: java.util.Date
45 | get() = get("gMonthDay")!!
46 | set(value) { set("gMonthDay", value) }
47 | var `gYear`: java.util.Date
48 | get() = get("gYear")!!
49 | set(value) { set("gYear", value) }
50 | var `gYearMonth`: java.util.Date
51 | get() = get("gYearMonth")!!
52 | set(value) { set("gYearMonth", value) }
53 | var `hexBinary`: kotlin.ByteArray
54 | get() = get("hexBinary")!!
55 | set(value) { set("hexBinary", value) }
56 | var `int`: kotlin.Int
57 | get() = get("int")!!
58 | set(value) { set("int", value) }
59 | var `integer`: kotlin.Int
60 | get() = get("integer")!!
61 | set(value) { set("integer", value) }
62 | var `long`: kotlin.Long
63 | get() = get("long")!!
64 | set(value) { set("long", value) }
65 | var `negativeInteger`: kotlin.Int
66 | get() = get("negativeInteger")!!
67 | set(value) { set("negativeInteger", value) }
68 | var `nonNegativeInteger`: kotlin.Int
69 | get() = get("nonNegativeInteger")!!
70 | set(value) { set("nonNegativeInteger", value) }
71 | var `nonPositiveInteger`: kotlin.Int
72 | get() = get("nonPositiveInteger")!!
73 | set(value) { set("nonPositiveInteger", value) }
74 | var `positiveInteger`: kotlin.Int
75 | get() = get("positiveInteger")!!
76 | set(value) { set("positiveInteger", value) }
77 | var `string`: kotlin.String
78 | get() = get("string")!!
79 | set(value) { set("string", value) }
80 | var `short`: kotlin.Short
81 | get() = get("short")!!
82 | set(value) { set("short", value) }
83 | var `time`: java.util.Date
84 | get() = get("time")!!
85 | set(value) { set("time", value) }
86 | var `token`: kotlin.String
87 | get() = get("token")!!
88 | set(value) { set("token", value) }
89 | var `unsignedByte`: kotlin.Short
90 | get() = get("unsignedByte")!!
91 | set(value) { set("unsignedByte", value) }
92 | var `unsignedInt`: kotlin.Long
93 | get() = get("unsignedInt")!!
94 | set(value) { set("unsignedInt", value) }
95 | var `unsignedLong`: kotlin.Int
96 | get() = get("unsignedLong")!!
97 | set(value) { set("unsignedLong", value) }
98 | var `unsignedShort`: kotlin.Int
99 | get() = get("unsignedShort")!!
100 | set(value) { set("unsignedShort", value) }
101 | }
102 |
103 | fun `attributesRequired`(`uri`: kotlin.String,
104 | `base64Binary`: kotlin.ByteArray,
105 | `boolean`: kotlin.Boolean,
106 | `byte`: kotlin.Byte,
107 | `date`: java.util.Date,
108 | `dateTime`: java.util.Date,
109 | `decimal`: java.math.BigDecimal,
110 | `double`: kotlin.Double,
111 | `duration`: javax.xml.datatype.Duration,
112 | `float`: kotlin.Float,
113 | `gDay`: java.util.Date,
114 | `gMonth`: java.util.Date,
115 | `gMonthDay`: java.util.Date,
116 | `gYear`: java.util.Date,
117 | `gYearMonth`: java.util.Date,
118 | `hexBinary`: kotlin.ByteArray,
119 | `int`: kotlin.Int,
120 | `integer`: kotlin.Int,
121 | `long`: kotlin.Long,
122 | `negativeInteger`: kotlin.Int,
123 | `nonNegativeInteger`: kotlin.Int,
124 | `nonPositiveInteger`: kotlin.Int,
125 | `positiveInteger`: kotlin.Int,
126 | `string`: kotlin.String,
127 | `short`: kotlin.Short,
128 | `time`: java.util.Date,
129 | `token`: kotlin.String,
130 | `unsignedByte`: kotlin.Short,
131 | `unsignedInt`: kotlin.Long,
132 | `unsignedLong`: kotlin.Int,
133 | `unsignedShort`: kotlin.Int,
134 | __block__: `AttributesRequired`.() -> Unit): `AttributesRequired` {
135 | val `attributesRequired` = `AttributesRequired`("attributesRequired")
136 | `attributesRequired`.apply {
137 | this.`uri` = `uri`
138 | this.`base64Binary` = `base64Binary`
139 | this.`boolean` = `boolean`
140 | this.`byte` = `byte`
141 | this.`date` = `date`
142 | this.`dateTime` = `dateTime`
143 | this.`decimal` = `decimal`
144 | this.`double` = `double`
145 | this.`duration` = `duration`
146 | this.`float` = `float`
147 | this.`gDay` = `gDay`
148 | this.`gMonth` = `gMonth`
149 | this.`gMonthDay` = `gMonthDay`
150 | this.`gYear` = `gYear`
151 | this.`gYearMonth` = `gYearMonth`
152 | this.`hexBinary` = `hexBinary`
153 | this.`int` = `int`
154 | this.`integer` = `integer`
155 | this.`long` = `long`
156 | this.`negativeInteger` = `negativeInteger`
157 | this.`nonNegativeInteger` = `nonNegativeInteger`
158 | this.`nonPositiveInteger` = `nonPositiveInteger`
159 | this.`positiveInteger` = `positiveInteger`
160 | this.`string` = `string`
161 | this.`short` = `short`
162 | this.`time` = `time`
163 | this.`token` = `token`
164 | this.`unsignedByte` = `unsignedByte`
165 | this.`unsignedInt` = `unsignedInt`
166 | this.`unsignedLong` = `unsignedLong`
167 | this.`unsignedShort` = `unsignedShort`
168 | }
169 | `attributesRequired`.apply(__block__)
170 | return `attributesRequired`
171 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/kotlin-xml-dsl-generator/src/test/resources/code/attributes.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("PropertyName", "ReplaceArrayOfWithLiteral", "LocalVariableName", "FunctionName", "RemoveRedundantBackticks")
2 |
3 | package org.redundent.generated
4 |
5 | import org.redundent.kotlin.xml.*
6 |
7 | open class `Attributes`(nodeName: String) : Node(nodeName) {
8 | var `uri`: kotlin.String?
9 | get() = get("uri")
10 | set(value) { set("uri", value) }
11 | var `base64Binary`: kotlin.ByteArray?
12 | get() = get("base64Binary")
13 | set(value) { set("base64Binary", value) }
14 | var `boolean`: kotlin.Boolean?
15 | get() = get("boolean")
16 | set(value) { set("boolean", value) }
17 | var `byte`: kotlin.Byte?
18 | get() = get("byte")
19 | set(value) { set("byte", value) }
20 | var `date`: java.util.Date?
21 | get() = get("date")
22 | set(value) { set("date", value) }
23 | var `dateTime`: java.util.Date?
24 | get() = get("dateTime")
25 | set(value) { set("dateTime", value) }
26 | var `decimal`: java.math.BigDecimal?
27 | get() = get("decimal")
28 | set(value) { set("decimal", value) }
29 | var `double`: kotlin.Double?
30 | get() = get("double")
31 | set(value) { set("double", value) }
32 | var `duration`: javax.xml.datatype.Duration?
33 | get() = get("duration")
34 | set(value) { set("duration", value) }
35 | var `float`: kotlin.Float?
36 | get() = get("float")
37 | set(value) { set("float", value) }
38 | var `gDay`: java.util.Date?
39 | get() = get("gDay")
40 | set(value) { set("gDay", value) }
41 | var `gMonth`: java.util.Date?
42 | get() = get("gMonth")
43 | set(value) { set("gMonth", value) }
44 | var `gMonthDay`: java.util.Date?
45 | get() = get("gMonthDay")
46 | set(value) { set("gMonthDay", value) }
47 | var `gYear`: java.util.Date?
48 | get() = get("gYear")
49 | set(value) { set("gYear", value) }
50 | var `gYearMonth`: java.util.Date?
51 | get() = get("gYearMonth")
52 | set(value) { set("gYearMonth", value) }
53 | var `hexBinary`: kotlin.ByteArray?
54 | get() = get("hexBinary")
55 | set(value) { set("hexBinary", value) }
56 | var `int`: kotlin.Int?
57 | get() = get("int")
58 | set(value) { set("int", value) }
59 | var `integer`: kotlin.Int?
60 | get() = get("integer")
61 | set(value) { set("integer", value) }
62 | var `long`: kotlin.Long?
63 | get() = get("long")
64 | set(value) { set("long", value) }
65 | var `negativeInteger`: kotlin.Int?
66 | get() = get("negativeInteger")
67 | set(value) { set("negativeInteger", value) }
68 | var `nonNegativeInteger`: kotlin.Int?
69 | get() = get("nonNegativeInteger")
70 | set(value) { set("nonNegativeInteger", value) }
71 | var `nonPositiveInteger`: kotlin.Int?
72 | get() = get("nonPositiveInteger")
73 | set(value) { set("nonPositiveInteger", value) }
74 | var `positiveInteger`: kotlin.Int?
75 | get() = get("positiveInteger")
76 | set(value) { set("positiveInteger", value) }
77 | var `string`: kotlin.String?
78 | get() = get("string")
79 | set(value) { set("string", value) }
80 | var `short`: kotlin.Short?
81 | get() = get("short")
82 | set(value) { set("short", value) }
83 | var `time`: java.util.Date?
84 | get() = get("time")
85 | set(value) { set("time", value) }
86 | var `token`: kotlin.String?
87 | get() = get("token")
88 | set(value) { set("token", value) }
89 | var `unsignedByte`: kotlin.Short?
90 | get() = get("unsignedByte")
91 | set(value) { set("unsignedByte", value) }
92 | var `unsignedInt`: kotlin.Long?
93 | get() = get("unsignedInt")
94 | set(value) { set("unsignedInt", value) }
95 | var `unsignedLong`: kotlin.Int?
96 | get() = get("unsignedLong")
97 | set(value) { set("unsignedLong", value) }
98 | var `unsignedShort`: kotlin.Int?
99 | get() = get("unsignedShort")
100 | set(value) { set("unsignedShort", value) }
101 | }
102 |
103 | @JvmOverloads
104 | fun `attributes`(`uri`: kotlin.String? = null,
105 | `base64Binary`: kotlin.ByteArray? = null,
106 | `boolean`: kotlin.Boolean? = null,
107 | `byte`: kotlin.Byte? = null,
108 | `date`: java.util.Date? = null,
109 | `dateTime`: java.util.Date? = null,
110 | `decimal`: java.math.BigDecimal? = null,
111 | `double`: kotlin.Double? = null,
112 | `duration`: javax.xml.datatype.Duration? = null,
113 | `float`: kotlin.Float? = null,
114 | `gDay`: java.util.Date? = null,
115 | `gMonth`: java.util.Date? = null,
116 | `gMonthDay`: java.util.Date? = null,
117 | `gYear`: java.util.Date? = null,
118 | `gYearMonth`: java.util.Date? = null,
119 | `hexBinary`: kotlin.ByteArray? = null,
120 | `int`: kotlin.Int? = null,
121 | `integer`: kotlin.Int? = null,
122 | `long`: kotlin.Long? = null,
123 | `negativeInteger`: kotlin.Int? = null,
124 | `nonNegativeInteger`: kotlin.Int? = null,
125 | `nonPositiveInteger`: kotlin.Int? = null,
126 | `positiveInteger`: kotlin.Int? = null,
127 | `string`: kotlin.String? = null,
128 | `short`: kotlin.Short? = null,
129 | `time`: java.util.Date? = null,
130 | `token`: kotlin.String? = null,
131 | `unsignedByte`: kotlin.Short? = null,
132 | `unsignedInt`: kotlin.Long? = null,
133 | `unsignedLong`: kotlin.Int? = null,
134 | `unsignedShort`: kotlin.Int? = null,
135 | __block__: `Attributes`.() -> Unit): `Attributes` {
136 | val `attributes` = `Attributes`("attributes")
137 | `attributes`.apply {
138 | if (`uri` != null) {
139 | this.`uri` = `uri`
140 | }
141 | if (`base64Binary` != null) {
142 | this.`base64Binary` = `base64Binary`
143 | }
144 | if (`boolean` != null) {
145 | this.`boolean` = `boolean`
146 | }
147 | if (`byte` != null) {
148 | this.`byte` = `byte`
149 | }
150 | if (`date` != null) {
151 | this.`date` = `date`
152 | }
153 | if (`dateTime` != null) {
154 | this.`dateTime` = `dateTime`
155 | }
156 | if (`decimal` != null) {
157 | this.`decimal` = `decimal`
158 | }
159 | if (`double` != null) {
160 | this.`double` = `double`
161 | }
162 | if (`duration` != null) {
163 | this.`duration` = `duration`
164 | }
165 | if (`float` != null) {
166 | this.`float` = `float`
167 | }
168 | if (`gDay` != null) {
169 | this.`gDay` = `gDay`
170 | }
171 | if (`gMonth` != null) {
172 | this.`gMonth` = `gMonth`
173 | }
174 | if (`gMonthDay` != null) {
175 | this.`gMonthDay` = `gMonthDay`
176 | }
177 | if (`gYear` != null) {
178 | this.`gYear` = `gYear`
179 | }
180 | if (`gYearMonth` != null) {
181 | this.`gYearMonth` = `gYearMonth`
182 | }
183 | if (`hexBinary` != null) {
184 | this.`hexBinary` = `hexBinary`
185 | }
186 | if (`int` != null) {
187 | this.`int` = `int`
188 | }
189 | if (`integer` != null) {
190 | this.`integer` = `integer`
191 | }
192 | if (`long` != null) {
193 | this.`long` = `long`
194 | }
195 | if (`negativeInteger` != null) {
196 | this.`negativeInteger` = `negativeInteger`
197 | }
198 | if (`nonNegativeInteger` != null) {
199 | this.`nonNegativeInteger` = `nonNegativeInteger`
200 | }
201 | if (`nonPositiveInteger` != null) {
202 | this.`nonPositiveInteger` = `nonPositiveInteger`
203 | }
204 | if (`positiveInteger` != null) {
205 | this.`positiveInteger` = `positiveInteger`
206 | }
207 | if (`string` != null) {
208 | this.`string` = `string`
209 | }
210 | if (`short` != null) {
211 | this.`short` = `short`
212 | }
213 | if (`time` != null) {
214 | this.`time` = `time`
215 | }
216 | if (`token` != null) {
217 | this.`token` = `token`
218 | }
219 | if (`unsignedByte` != null) {
220 | this.`unsignedByte` = `unsignedByte`
221 | }
222 | if (`unsignedInt` != null) {
223 | this.`unsignedInt` = `unsignedInt`
224 | }
225 | if (`unsignedLong` != null) {
226 | this.`unsignedLong` = `unsignedLong`
227 | }
228 | if (`unsignedShort` != null) {
229 | this.`unsignedShort` = `unsignedShort`
230 | }
231 | }
232 | `attributes`.apply(__block__)
233 | return `attributes`
234 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://maven-badges.herokuapp.com/maven-central/org.redundent/kotlin-xml-builder)
3 |
4 | Kotlin XML Builder
5 | =
6 |
7 | This library can be used to build xml documents from Kotlin code.
8 | It is based on the HTML builder described in the [Kotlin docs](https://kotlinlang.org/docs/reference/type-safe-builders.html)
9 |
10 | It is designed to be lightweight and fast. There isn't any validation except to
11 | escape text to not violate xml standards.
12 |
13 | License
14 | -
15 | Apache 2.0
16 |
17 | Usage
18 | =
19 | To use in Gradle, simply add the Maven Central repository and then add the following dependency.
20 | ```gradle
21 | repositories {
22 | mavenCentral()
23 | }
24 |
25 | dependencies {
26 | compile("org.redundent:kotlin-xml-builder:[VERSION]")
27 | }
28 | ```
29 |
30 | Similarly in Maven:
31 | ```xml
32 |
33 |
34 | org.redundent
35 | kotlin-xml-builder
36 | [VERSION]
37 |
38 |
39 | ```
40 |
41 | Example
42 | =
43 | ```kotlin
44 | val people = xml("people") {
45 | xmlns = "http://example.com/people"
46 | "person" {
47 | attribute("id", 1)
48 | "firstName" {
49 | -"John"
50 | }
51 | "lastName" {
52 | -"Doe"
53 | }
54 | "phone" {
55 | -"555-555-5555"
56 | }
57 | }
58 | }
59 |
60 | val asString = people.toString()
61 | ```
62 | produces
63 | ```xml
64 |
65 |
66 |
67 | John
68 |
69 |
70 | Doe
71 |
72 |
73 | 555-555-5555
74 |
75 |
76 |
77 | ```
78 |
79 | ```kotlin
80 | class Person(val id: Long, val firstName: String, val lastName: String, val phone: String)
81 |
82 | val listOfPeople = listOf(
83 | Person(1, "John", "Doe", "555-555-5555"),
84 | Person(2, "Jane", "Doe", "555-555-6666")
85 | )
86 |
87 | val people = xml("people") {
88 | xmlns = "http://example.com/people"
89 | for (person in listOfPeople) {
90 | "person" {
91 | attribute("id", person.id)
92 | "firstName" {
93 | -person.firstName
94 | }
95 | "lastName" {
96 | -person.lastName
97 | }
98 | "phone" {
99 | -person.phone
100 | }
101 | }
102 | }
103 | }
104 |
105 | val asString = people.toString()
106 | ```
107 | produces
108 | ```xml
109 |
110 |
111 |
112 | John
113 |
114 | Doe
116 |
117 |
118 | 555-555-5555
119 |
120 |
121 |
122 |
123 | Jane
124 |
125 |
126 | Doe
127 |
128 |
129 | 555-555-6666
130 |
131 |
132 |
133 | ```
134 |
135 | ### Namespaces
136 | Version 1.8.0 added more precise control over how namespaces are used. You can add a namespace to any element or attribute
137 | and the correct node/attribute name will be used automatically. When using the new namespace aware methods, you no longer
138 | need to manually add the namespace to the element.
139 |
140 | See examples of `< 1.8.0` and `>= 1.8.0` below to produce the following xml
141 |
142 | ```xml
143 |
144 |
145 |
146 | ```
147 |
148 | #### < 1.8.0
149 | ```kotlin
150 | xml("t:root") {
151 | namespace("t", "https://ns.org")
152 | "t:element"("t:key" to "value")
153 | }
154 | ```
155 |
156 | #### >= 1.8.0
157 | ```kotlin
158 | val ns = Namespace("t", "https://ns.org")
159 | xml("root", ns) {
160 | "element"(ns, Attribute("key", "value", ns))
161 | }
162 | ```
163 |
164 | You can also use the `Namespace("https://ns.org")` constructor to create a Namespace object that represents the default xmlns.
165 |
166 | #### Things to be aware of
167 |
168 | * Previously, all namespace declarations would get added to the attributes maps immediately. That no long happens. All
169 | namespaces get added at render time. To retrieve a list of the current namespaces of a node, use the `namespaces` property.
170 | * When a namespace is provided for a node or attribute, it will be declared on that element IF it is not already declared
171 | on the root element or one of the element's parents.
172 |
173 | ### Processing Instructions
174 | You can add processing instructions to any element by using the `processingInstruction` method.
175 |
176 | ```kotlin
177 | xml("root") {
178 | processingInstruction("instruction")
179 | }
180 | ```
181 |
182 | ```xml
183 |
184 |
185 |
186 | ```
187 |
188 | #### Global Instructions
189 | Similarly you can add a global (top-level) instruction by call `globalProcessingInstruction` on the
190 | root node. This method only applies to the root. If it is called on any other element, it will be ignored.
191 |
192 |
193 | ```kotlin
194 | xml("root") {
195 | globalProcessingInstruction("xml-stylesheet", "type" to "text/xsl", "href" to "style.xsl")
196 | }
197 | ```
198 |
199 | ```xml
200 |
201 |
202 | ```
203 |
204 | ## DOCTYPE
205 |
206 | As of version 1.7.4, you can specify a DTD (Document Type Declaration).
207 |
208 | ```kotlin
209 | xml("root") {
210 | doctype(systemId = "mydtd.dtd")
211 | }
212 | ```
213 |
214 | ```xml
215 |
216 |
217 | ```
218 |
219 | ### Limitations with DTD
220 |
221 | Complex DTDs are not supported.
222 |
223 | ## Unsafe
224 | You can now use unsafe text for element and attribute values.
225 | ```kotlin
226 | xml("root") {
227 | unsafeText("")
228 | }
229 | ```
230 | produces
231 | ```xml
232 |
233 |
234 |
235 | ```
236 |
237 | ```kotlin
238 | xml("root") {
239 | attribute("attr", unsafe("{"))
240 | }
241 | ```
242 | produces
243 | ```xml
244 |
245 | ```
246 |
247 | ## Print Options
248 | You can now control how your xml will look when rendered by passing the new PrintOptions class as an argument to `toString`.
249 |
250 | `pretty` - This is the default and will produce the xml you see above.
251 |
252 | `singleLineTextElements` - This will render single text element nodes on a single line if `pretty` is true
253 | ```xml
254 |
255 | value
256 |
257 | ```
258 | as opposed to:
259 | ```xml
260 |
261 |
262 | value
263 |
264 |
265 | ```
266 | `useSelfClosingTags` - Use `` instead of `` for empty tags
267 |
268 | `useCharacterReference` - Use character references instead of escaped characters. i.e. `'` instead of `'`
269 |
270 | ## Reading XML
271 | You can also read xml documents using the `parse` methods. They provide basic
272 | xml parsing and will build a `Node` element to build upon.
273 |
274 | For more advanced consuming, check out [konsume-xml](https://gitlab.com/mvysny/konsume-xml).
275 | It includes many more features for consuming documents.
276 |
277 | Release Notes
278 | =============
279 | Version 1.9.3
280 | -
281 | * Re-fixing issue where text elements are ignored when they are just whitespace
282 |
283 | Version 1.9.2
284 | -
285 | * Fixing issue where text elements are ignored when they are just whitespace\
286 | Thanks to [@fiddlededee](https://github.com/fiddlededee) for finding this!
287 |
288 | Version 1.9.1
289 | -
290 | * Adding `addElement`, `addElements`, `addElementsBefore`, `addElementsAfter`, `removeElement`,
291 | `removeElements`, and `replaceElement` to Node.\
292 | Thanks to [@csmile2](https://github.com/csmile2) for requesting this!
293 | * Deprecating `addNode`, `addNodeBefore`, `addNodeAfter`, `removeNode`, and `replaceNode` in favor of Element methods.
294 | * Adding ktlint checks
295 |
296 | Version 1.9.0
297 | -
298 | * Adding `unsafe` and `unsafeText` methods to allow for unescaped values in elements and attributes.\
299 | Thanks to [@krzysztofsroga](https://github.com/krzysztofsroga) for requesting this!
300 |
301 | **BREAKING CHANGES**
302 | * The `attributes` property on a node will now return an immutable map (`Map` instead of `LinkedHashMap`). This property can no longer be used to\
303 | manipulate the attributes. Use `set`/`removeAttribute` for that.
304 |
305 | Version 1.8.0
306 | -
307 | * Adding more precise functionality for xml namespaces, allowing typesafe building of elements and attributes with namespaces.\
308 | Thanks to [@asm0dey](https://github.com/asm0dey) for requesting this!
309 |
310 | Version 1.7.4
311 | -
312 | * Adding ability to add a DTD.\
313 | Thanks to [@anskotid](https://github.com/anskotid) for raising this!
314 |
315 | Version 1.7.3
316 | -
317 | * Making `private fun parse(Document)` public.\
318 | Thanks to [@rkklai](https://github.com/rkklai) for requesting this!
319 |
320 | Version 1.7.2
321 | -
322 | * Fixing issue where a text element that contains an empty string doesn't respect `useSelfClosingTags`.\
323 | Thanks to [@d-wojciechowski](https://github.com/d-wojciechowski) for finding this!
324 |
325 | Version 1.7.1
326 | -
327 | * Added new PrintOptions to control the indent character(s) used. `indent` default is `\t`.\
328 | Thanks to [@vRallev](https://github.com/vRallev) for adding this!
329 |
330 | Version 1.7
331 | -
332 | **POTENTIAL BREAKING CHANGES**
333 | * All node types override `equals` and `hashCode`. This could change the behavior of putting nodes in a Set or Map.\
334 | Thanks to [@cbartolome](https://github.com/cbartolome) for requesting this!
335 |
336 | Version 1.6.1
337 | -
338 | * Added new PrintOptions to use character references instead of HTML names.\
339 | Thanks to [@senecal-jjs](https://github.com/senecal-jjs) and [@sleddog](https://github.com/sleddor) for
340 | adding this!
341 |
342 | Version 1.6.0
343 | -
344 | * Updated README
345 |
346 | **BREAKING CHANGES**
347 | * The `kotlin-reflect` dependency has been removed from the transitive dependnecies.
348 | This module is only used for controlling element order using `@XmlType`.
349 | If your project depends on that feature, you will need to have `kotlin-reflect` on the
350 | runtime classpath.\
351 | Thanks to [@mvysny](https://github.com/mvysny) for requesting this!
352 |
353 | Version 1.5.4
354 | -
355 | * Adding new global processing instructions.\
356 | Thanks to [@rjaros](https://github.com/rjaros) for requsting this!
357 |
358 | Version 1.5.3
359 | -
360 | * Fixing single line text element rendering for processing instructures and CData elements.\
361 | Thanks to [@jonathan-yan](https://github.com/jonathan-yan) for fixing this!
362 |
363 | Version 1.5.2
364 | -
365 | * Added ability to specify the xml version. This affects both the prolog and text escaping.\
366 | Thanks to [@ZR8C](https://github.com/ZR8C) for finding this!
367 |
368 | Version 1.5.1
369 | -
370 | * Added ability to add processing instructions. Use the new `processingInstruction` method to do this.\
371 | Thanks to [@endofhome](https://github.com/endofhome) for adding this!
372 | * Added ability to add comments. Use the new `comment` method to do this.\
373 | Thanks to [@ejektaflex](https://github.com/ejektaflex) for requesting this!
374 |
375 | Version 1.5.0
376 | -
377 | * Added more robust PrintOptions class to allow for more control over how xml is structured.\
378 | Fixes issue #16
379 | * Attribute values are now fully escaped properly.\
380 | Thanks to [@pkulak](https://github.com/pkulak) for finding and fixing this!
381 |
382 | **BREAKING CHANGES**
383 | * Changed Element.render method signature to use kotlin.text.Appenable instead of kotlin.text.StringBuilder.
384 | Any custom element types created will need to be updated
385 |
386 | Version 1.4.5
387 | -
388 | * Fixed incorrect handling of CDATA elements. `prettyFormat` should not alter the CDATA content.
389 | * Fixed nested CData elements\
390 | Big thanks to [@TWiStErRob](https://github.com/TWiStErRob) for finding and fixing both of these!
391 |
392 | Version 1.4.4
393 | -
394 | * Switched to Kotlin's `Charsets` instead of using `java.nio.charset.StandardCharsets`. `StandardCharsets` is not available in some versions of Android.\
395 | Thanks to [@little-fish](https://github.com/little-fish) for submitting and fixing this!
396 |
397 | Version 1.4.3
398 | -
399 | **BREAKING CHANGES**
400 | * Moved `prettyFormat` to a parameter of `toString()`. `prettyFormat` in the constructor or util method is no longer available. Fixes issue #7
401 |
402 | Version 1.4.2
403 | -
404 | * Fixes issue #6
405 |
406 | Version 1.4.1
407 | -
408 | * Upgrading Gradle to 4.9
409 | * Upgrading Kotlin to 1.2.60
410 | * Fix issue #4
411 | * Tweak generation code to be more concise
412 | * Add flag to code generation to allow for member functions instead of extension functions
413 |
414 | Version 1.4
415 | -
416 | **BREAKING CHANGES**
417 | * `org.redundent.kotlin.xml.Node.name` has been renamed to `org.redundent.kotlin.xml.Node.nodeName` to avoid clashes with attributes called `name`.
418 | ---
419 | * Adding DSL generator project to generate kotlin-xml-builder DSL from a schema file.
420 | See [kotlin-xml-dsl-generator](./kotlin-xml-dsl-generator) for details
421 |
422 | Version 1.3
423 | -
424 | * Added ability to parse an xml document into a builder object using the new `parse` method.
425 | * Upgrading Gradle and Kotlin versions.
426 |
427 | Version 1.2
428 | -
429 | * Added a `sitemap` method to allow for easy generation of sitemaps (which is what this project was created for in the first place).
430 | * Added `String.invoke` for elements allowing you to specify elements by just their name (see docs above)
431 | * Added some searching methods to search child nodes. `filter`, `first`, `firstOrNull`, and `exists`.
432 | * Added some mutation methods to allow you to add/remove/replace nodes in an element.
433 |
434 | Version 1.1
435 | -
436 | * Added convenience method for elements with just a name and value. `element("name", "value")`
437 | * Removed `ns` method. Please use `namespace(...)` instead.
438 | * Upgraded Gradle version
439 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/test/kotlin/org/redundent/kotlin/xml/XmlBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.junit.Test
4 | import org.xml.sax.SAXException
5 | import java.io.ByteArrayInputStream
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertFailsWith
8 | import kotlin.test.assertFalse
9 | import kotlin.test.assertNotNull
10 | import kotlin.test.assertNull
11 | import kotlin.test.assertTrue
12 |
13 | class XmlBuilderTest : TestBase() {
14 | @Test
15 | fun basicTest() {
16 | val urlset = xml("urlset") {
17 | xmlns = "https://www.sitemaps.org/schemas/sitemap/0.9"
18 |
19 | for (i in 0..2) {
20 | element("url") {
21 | element("loc") {
22 | -"https://google.com/$i"
23 | }
24 | }
25 | }
26 | }
27 |
28 | validate(urlset)
29 | }
30 |
31 | @Test
32 | fun customNamespaces() {
33 | val root = xml("root") {
34 | xmlns = "https://someurl.org"
35 | namespace("t", "https://t.org")
36 |
37 | element("t:element") {
38 | -"Test"
39 | }
40 |
41 | element("p") {
42 | xmlns = "https://t.co"
43 | }
44 |
45 | element("d:p") {
46 | namespace("d", "https://b.co")
47 | }
48 | }
49 |
50 | validate(root)
51 | }
52 |
53 | @Test
54 | fun notPrettyFormatting() {
55 | val root = xml("root") {
56 | element("element") {
57 | -"Hello"
58 | }
59 | element("otherElement") {
60 | -"Test"
61 | }
62 | }
63 |
64 | validate(root, prettyFormat = false)
65 | }
66 |
67 | @Test
68 | fun zeroSpaceIndent() {
69 | val root = xml("root") {
70 | element("element") {
71 | -"Hello"
72 | }
73 | element("otherElement") {
74 | -"Test"
75 | }
76 | }
77 |
78 | validate(root, PrintOptions(indent = ""))
79 | }
80 |
81 | @Test
82 | fun zeroSpaceIndentNoPrettyFormatting() {
83 | val root = xml("root") {
84 | element("element") {
85 | -"Hello"
86 | }
87 | element("otherElement") {
88 | -"Test"
89 | }
90 | }
91 |
92 | validate(root, PrintOptions(pretty = false, indent = ""))
93 | }
94 |
95 | @Test
96 | fun singleLineTextElement() {
97 | val root = xml("root") {
98 | element("element") {
99 | -"Hello"
100 | }
101 | element("otherElement") {
102 | -"Test"
103 | }
104 | }
105 |
106 | validate(root, PrintOptions(pretty = true, singleLineTextElements = true))
107 | }
108 |
109 | @Test
110 | fun singleLineCDATAElement() {
111 | val root = xml("root") {
112 | element("element") {
113 | cdata("Some & xml")
114 | }
115 | }
116 |
117 | validate(root, PrintOptions(pretty = true, singleLineTextElements = true))
118 | }
119 |
120 | @Test
121 | fun singleLineProcessingInstructionElement() {
122 | val root = xml("root") {
123 | element("element") {
124 | processingInstruction("SomeProcessingInstruction")
125 | }
126 | }
127 |
128 | validate(root, PrintOptions(pretty = true, singleLineTextElements = true))
129 | }
130 |
131 | @Test
132 | fun singleLineProcessingInstructionElementWithAttributes() {
133 | val root = xml("root") {
134 | element("element") {
135 | processingInstruction("SomeProcessingInstruction", "key" to "value")
136 | }
137 | }
138 |
139 | validate(root, PrintOptions(pretty = true, singleLineTextElements = true))
140 | }
141 |
142 | @Test
143 | fun globalProcessingInstructionElement() {
144 | val root = xml("root") {
145 | globalProcessingInstruction(
146 | "xml-stylesheet",
147 | "key" to "value",
148 | "href" to "http://blah"
149 | )
150 |
151 | element("element") {
152 | globalProcessingInstruction("test")
153 | }
154 | }
155 |
156 | validate(root, PrintOptions(pretty = true, singleLineTextElements = true))
157 | }
158 |
159 | @Test
160 | fun comment() {
161 | val root = xml("root") {
162 | comment("my comment -->")
163 | element("someNode") {
164 | -"value"
165 | }
166 | }
167 |
168 | validate(root)
169 | }
170 |
171 | @Test
172 | fun noSelfClosingTag() {
173 | val root = xml("root") {
174 | element("element")
175 | }
176 |
177 | validate(root, PrintOptions(useSelfClosingTags = false))
178 | }
179 |
180 | @Test
181 | fun multipleAttributes() {
182 | val root = xml("root") {
183 | element("test") {
184 | attribute("key", "value")
185 | attribute("otherAttr", "hello world")
186 | }
187 | element("attributes") {
188 | attributes(
189 | "test" to "value",
190 | "key" to "pair"
191 | )
192 | }
193 | }
194 |
195 | validate(root)
196 | }
197 |
198 | @Test
199 | fun emptyRoot() {
200 | validate(xml("root"))
201 | }
202 |
203 | @Test
204 | fun emptyElement() {
205 | validate(
206 | xml("root") {
207 | element("test")
208 | }
209 | )
210 | }
211 |
212 | @Test
213 | fun cdata() {
214 | val root = xml("root") {
215 | cdata("Some & xml")
216 | }
217 |
218 | validate(root)
219 | }
220 |
221 | @Test
222 | fun cdataNesting() {
223 | val root = xml("root") {
224 | cdata("")
225 | }
226 |
227 | validate(root)
228 | }
229 |
230 | @Test
231 | fun processingInstruction() {
232 | val root = xml("root") {
233 | processingInstruction("SomeProcessingInstruction")
234 | }
235 |
236 | validate(root)
237 | }
238 |
239 | @Test
240 | fun updateAttribute() {
241 | val root = xml("root") {
242 | attribute("key", "value")
243 | }
244 |
245 | root["key"] = "otherValue"
246 |
247 | validate(root)
248 | }
249 |
250 | @Test
251 | fun xmlEncode() {
252 | val root = xml("root") {
253 | -"&<>"
254 | }
255 |
256 | validate(root)
257 | }
258 |
259 | @Test
260 | fun elementValue() {
261 | val root = xml("root") {
262 | element("name", "value")
263 | }
264 |
265 | validate(root)
266 | }
267 |
268 | @Test
269 | fun elementAsString() {
270 | val root = xml("root") {
271 | "name"("value")
272 | }
273 |
274 | validate(root)
275 | }
276 |
277 | @Test
278 | fun elementAsStringWithAttributes() {
279 | validate(
280 | xml("root") {
281 | "name"("attr" to "value", "attr2" to "other")
282 | }
283 | )
284 | }
285 |
286 | @Test
287 | fun elementAsStringWithAttributesAndContent() {
288 | validate(
289 | xml("root") {
290 | "name"("attr" to "value") {
291 | -"Content"
292 | }
293 | }
294 | )
295 | }
296 |
297 | @Test
298 | fun attributes() {
299 | val xmlns = "testing"
300 | val value = "value"
301 |
302 | val xml = xml("root") {
303 | this.xmlns = xmlns
304 | attribute("attr", value)
305 | }
306 |
307 | assertEquals(xmlns, xml.xmlns, "xmlns is correct")
308 | assertNotNull(xml["attr"], "attr is not null")
309 | assertEquals(value, xml["attr"]!!, "attr getting is correct")
310 |
311 | // Update the attr value
312 | xml["attr"] = "something else"
313 | assertEquals("something else", xml["attr"]!!, "attr value is updated")
314 |
315 | // Remove the
316 | xml.xmlns = null
317 | assertNull(xml.xmlns, "xmlns is removed")
318 |
319 | xml["attr"] = null
320 | assertFalse(xml.attributes.containsKey("attr"))
321 | assertNull(xml["attr"], "attr value is null")
322 | }
323 |
324 | @Test
325 | fun quoteInAttribute() {
326 | val root = xml("root") {
327 | attribute("attr", "My \" Attribute value '")
328 | }
329 |
330 | validate(root)
331 | }
332 |
333 | @Test
334 | fun specialCharInAttribute() {
335 | val root = xml("root") {
336 | attribute("attr", "& < > \" '")
337 | }
338 |
339 | validate(root)
340 | }
341 |
342 | @Test(expected = SAXException::class)
343 | fun invalidElementName() {
344 | val root = xml("invalid root")
345 |
346 | validateXml(root.toString())
347 | }
348 |
349 | @Test(expected = SAXException::class)
350 | fun invalidAttributeName() {
351 | val root = xml("root") {
352 | attribute("invalid name", "")
353 | }
354 |
355 | validateXml(root.toString())
356 | }
357 |
358 | @Test
359 | fun filterFunctions() {
360 | val xml = xml("root") {
361 | "child1" {
362 | "other"()
363 | }
364 | "child2"()
365 | "multiple"()
366 | "multiple"()
367 | }
368 |
369 | val child1 = xml.filter("child1")
370 | assertEquals(1, child1.size, "filter returned one element")
371 |
372 | val hasChild = xml.filter { it.nodeName == "child1" && it.exists("other") }
373 | assertEquals(1, hasChild.size, "filter with exists returned one element")
374 |
375 | val multiple = xml.filter("multiple")
376 | assertEquals(2, multiple.size, "filter with multiple returned two element")
377 |
378 | assertNull(xml.firstOrNull("junk"), "firstOrNull returned null")
379 | assertNotNull(xml.firstOrNull("child1"), "firstOrNull returned element")
380 |
381 | assertFailsWith(NoSuchElementException::class) {
382 | xml.first("junk")
383 | }
384 |
385 | assertTrue("element exists") { xml.exists("child1") }
386 | assertFalse("element doesn't exists") { xml.exists("junk") }
387 | }
388 |
389 | @Test
390 | fun addElement() {
391 | val root = xml("root") {
392 | "a"()
393 | }
394 |
395 | root.addElement(node("b"))
396 |
397 | validate(root)
398 | }
399 |
400 | @Test
401 | fun removeElement() {
402 | val root = xml("root") {
403 | "a"()
404 | "b"()
405 | }
406 |
407 | root.removeElement(root.first("b"))
408 |
409 | validate(root)
410 | }
411 |
412 | @Test
413 | fun addElementAfter() {
414 | val root = xml("root") {
415 | "a"()
416 | "b"()
417 | }
418 |
419 | root.addElementAfter(node("c"), root.first("a"))
420 |
421 | validate(root)
422 | }
423 |
424 | @Test
425 | fun addElementAfterLastChild() {
426 | val root = xml("root") {
427 | "a"()
428 | "b"()
429 | }
430 |
431 | root.addElementAfter(node("c"), root.first("b"))
432 |
433 | validate(root)
434 | }
435 |
436 | @Test(expected = IllegalArgumentException::class)
437 | fun addElementAfterNonExistent() {
438 | val root = xml("root") {
439 | "a"()
440 | "b"()
441 | }
442 |
443 | root.addElementAfter(node("c"), node("d"))
444 | }
445 |
446 | @Test
447 | fun addElementBefore() {
448 | val root = xml("root") {
449 | "a"()
450 | "b"()
451 | }
452 |
453 | root.addElementBefore(node("c"), root.first("b"))
454 |
455 | validate(root)
456 | }
457 |
458 | @Test(expected = IllegalArgumentException::class)
459 | fun addElementBeforeNonExistent() {
460 | val root = xml("root") {
461 | "a"()
462 | "b"()
463 | }
464 |
465 | root.addElementBefore(node("c"), node("d"))
466 | }
467 |
468 | @Test
469 | fun replaceElement() {
470 | val root = xml("root") {
471 | "a"()
472 | "b"()
473 | }
474 |
475 | root.replaceElement(root.first("b"), node("c"))
476 |
477 | validate(root)
478 | }
479 |
480 | @Test
481 | fun parseAndVerify() {
482 | val xmlns = "https://blog.redundent.org"
483 | val value = "value"
484 | val input = ByteArrayInputStream("$value".toByteArray())
485 |
486 | val root = parse(input)
487 |
488 | assertEquals("root", root.nodeName, "root element nodeName is correct")
489 | assertEquals(xmlns, root.xmlns, "root xmlns is correct")
490 |
491 | val children = root.children
492 | assertEquals(1, children.size, "root has 1 child")
493 | assertTrue(children[0] is Node, "child is a node")
494 |
495 | val child = children.first() as Node
496 | assertTrue(child.children[0] is TextElement, "element is text")
497 | assertEquals(value, (child.children[0] as TextElement).text)
498 | }
499 |
500 | @Test
501 | fun parseCData() = parseTest()
502 |
503 | @Test
504 | fun parseCDataWhitespace() = parseTest()
505 |
506 | @Test
507 | fun parseCustomNamespaces() = parseTest()
508 |
509 | @Test
510 | fun parseMultipleAttributes() = parseTest()
511 |
512 | @Test
513 | fun parseBasicTest() = parseTest()
514 |
515 | @Test
516 | fun parseXmlEncode() = parseTest()
517 |
518 | private fun parseTest() {
519 | val input = getInputStream()
520 | val xml = parse(input)
521 |
522 | validateTest(xml)
523 | }
524 |
525 | @Test
526 | fun checkIncludeXmlPrologFlag() {
527 | val node = xml("test")
528 | assertFalse(node.includeXmlProlog, "prolog is false")
529 |
530 | node.encoding = "UTF-8"
531 | assertTrue(node.includeXmlProlog, "prolog is included")
532 | }
533 |
534 | @Test
535 | fun encoding() {
536 | val xml = xml("test", encoding = "UTF-16").toString(prettyFormat = false)
537 |
538 | assertEquals("", xml)
539 | }
540 |
541 | @Test
542 | fun xmlVersion() {
543 | for (version in XmlVersion.values()) {
544 | val xml = xml("test", version = version).toString(prettyFormat = false)
545 | assertEquals("", xml)
546 | }
547 | }
548 |
549 | @Test
550 | fun characterReference() {
551 | val root = xml("root") {
552 | element("element") {
553 | -"Hello & Goodbye"
554 | }
555 | element("otherElement") {
556 | -"Test"
557 | }
558 | }
559 |
560 | validate(root, PrintOptions(pretty = true, singleLineTextElements = true, useCharacterReference = true))
561 | }
562 |
563 | @Test
564 | fun selfClosingTag() {
565 | for (text in arrayOf("", null)) {
566 | val root = xml("root") {
567 | "element" {
568 | if (text != null) {
569 | -text
570 | }
571 | }
572 | }
573 |
574 | validate(root, PrintOptions(pretty = true, useSelfClosingTags = true))
575 | }
576 | }
577 |
578 | @Test
579 | fun doctypeSimple() {
580 | val root = xml("root") {
581 | doctype()
582 | }
583 |
584 | validate(root)
585 | }
586 |
587 | @Test
588 | fun doctypeSystem() {
589 | val root = xml("root") {
590 | doctype(systemId = "test.dtd")
591 | }
592 |
593 | validate(root)
594 | }
595 |
596 | @Test
597 | fun doctypePublic() {
598 | val root = xml("root") {
599 | doctype(publicId = "-//redundent//PUBLIC DOCTYPE//EN", systemId = "test.dtd")
600 | }
601 |
602 | validate(root)
603 | }
604 |
605 | @Test
606 | fun advancedNamespaces() {
607 | val ns1 = Namespace("a", "https://ns1.org")
608 | val ns2 = Namespace("https://ns2.org")
609 | val ns3 = Namespace("b", "https://ns3.org")
610 | val ns4 = Namespace("c", "https://ns4.org")
611 | val ns5 = Namespace("d", "https://ns5.org")
612 | val ns6 = Namespace("e", "https://ns6.org")
613 |
614 | val root = xml("root", namespace = ns1) {
615 | namespace(ns2)
616 | "node"(ns3) {
617 | attribute("attr1", "value")
618 | attribute("attr2", "value", ns4)
619 | }
620 |
621 | "child" {
622 | attributes(
623 | ns5,
624 | "key1" to "value1",
625 | "key2" to "value2"
626 | )
627 | attributes(
628 | Attribute("key3", "value3", ns6),
629 | Attribute("key4", "value4", ns1)
630 | )
631 |
632 | "sub"(ns5, Attribute("key5", "value5", ns6))
633 | }
634 | }
635 |
636 | validate(root)
637 | }
638 |
639 | @Test
640 | fun unsafeAttributeValue() {
641 | val root = xml("root") {
642 | unsafeText("{")
643 | attribute("test", unsafe("Lj"))
644 | }
645 |
646 | validate(root)
647 | }
648 |
649 | @Test
650 | fun emptyString() {
651 | val root = xml("root") {
652 | "a"()
653 | -" "
654 | "b"()
655 | }
656 |
657 | validate(root, prettyFormat = false)
658 | }
659 |
660 | @Test
661 | fun whitespace() {
662 | val root = xml("root") {
663 | "a"(" ")
664 | "b"("\n")
665 | }
666 |
667 | validate(root, prettyFormat = false)
668 | }
669 | }
670 |
--------------------------------------------------------------------------------
/kotlin-xml-builder/src/main/kotlin/org/redundent/kotlin/xml/Node.kt:
--------------------------------------------------------------------------------
1 | package org.redundent.kotlin.xml
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder
4 | import org.apache.commons.lang3.builder.HashCodeBuilder
5 | import java.util.*
6 | import kotlin.collections.LinkedHashMap
7 | import kotlin.collections.LinkedHashSet
8 |
9 | /**
10 | * Base type for all elements. This is what handles pretty much all the rendering and building.
11 | */
12 | open class Node(val nodeName: String) : Element {
13 | private companion object {
14 | private val isReflectionAvailable: Boolean by lazy {
15 | Node::class.java.classLoader.getResource("kotlin/reflect/full") != null
16 | }
17 | }
18 |
19 | private var parent: Node? = null
20 | private val _globalLevelProcessingInstructions = ArrayList()
21 | private var doctype: Doctype? = null
22 | private val _namespaces: MutableSet = LinkedHashSet()
23 | private val _attributes: LinkedHashMap = LinkedHashMap()
24 | private val _children = ArrayList()
25 | private val childOrderMap: Map? by lazy {
26 | if (!isReflectionAvailable) {
27 | return@lazy null
28 | }
29 |
30 | val xmlTypeAnnotation = this::class.annotations.firstOrNull { it is XmlType } as? XmlType ?: return@lazy null
31 |
32 | val childOrder = xmlTypeAnnotation.childOrder
33 |
34 | childOrder.indices.associateBy { childOrder[it] }
35 | }
36 |
37 | val namespaces: Collection
38 | get() = LinkedHashSet(_namespaces)
39 |
40 | /**
41 | * The default `xmlns` for the document. To add other namespaces, use the [namespace] method
42 | */
43 | var xmlns: String?
44 | get() = namespaces.firstOrNull(Namespace::isDefault)?.value
45 | set(value) {
46 | if (value != null) {
47 | addNamespace(Namespace(value))
48 | } else {
49 | _namespaces.removeIf(Namespace::isDefault)
50 | }
51 | }
52 |
53 | /**
54 | * Whether to include the xml prolog, i.e. ``.
55 | *
56 | * NOTE: this only applies to the root element. It is ignored an all children.
57 | */
58 | var includeXmlProlog = false
59 |
60 | /**
61 | * Sets the encoding on the document. Setting this value will set [includeXmlProlog] to `true`.
62 | */
63 | var encoding: String = Charsets.UTF_8.name()
64 | set(value) {
65 | includeXmlProlog = true
66 | field = value
67 | }
68 |
69 | var version: XmlVersion = XmlVersion.V10
70 | set(value) {
71 | includeXmlProlog = true
72 | field = value
73 | }
74 |
75 | var standalone: Boolean? = null
76 | set(value) {
77 | includeXmlProlog = true
78 | field = value
79 | }
80 |
81 | /**
82 | * Any attributes that belong to this element. This will always return a copy of the attribute map.
83 | * @sample [set]
84 | */
85 | val attributes: Map
86 | get() = _attributes.mapValues {
87 | val value = it.value
88 | if (value is Unsafe) value.value else it.value
89 | }
90 |
91 | val children: List
92 | get() = _children
93 |
94 | private fun getParentNamespaces(): Set {
95 | return generateSequence(parent, Node::parent)
96 | .flatMap { it.namespaces.asSequence() }
97 | .toSet()
98 | }
99 |
100 | private fun initTag(tag: T, init: (T.() -> Unit)?): T {
101 | if (init != null) {
102 | tag.init()
103 | }
104 | _children.add(tag)
105 |
106 | setParentIfNode(tag, this)
107 |
108 | return tag
109 | }
110 |
111 | /**
112 | * Allows for easy access of this node's attributes
113 | *
114 | * ```
115 | * val attr = element["key"]
116 | * ```
117 | */
118 | operator fun get(attributeName: String): T? {
119 | val value = _attributes[attributeName]
120 | @Suppress("UNCHECKED_CAST")
121 | return (if (value is Unsafe) value.value else value) as T?
122 | }
123 |
124 | /**
125 | * Allows for easy access of adding/updating this node's attributes.
126 | * Setting the value of an attribute to `null` will remove the attribute.
127 | *
128 | * ```
129 | * element["key"] = "value"
130 | * ```
131 | */
132 | operator fun set(attributeName: String, value: Any?) {
133 | if (value == null) {
134 | removeAttribute(attributeName)
135 | } else {
136 | _attributes[attributeName] = value
137 | }
138 | }
139 |
140 | fun hasAttribute(attributeName: String): Boolean = _attributes.containsKey(attributeName)
141 |
142 | /**
143 | * Removes the specified attribute from the attributes map.
144 | */
145 | fun removeAttribute(attributeName: String) {
146 | _attributes.remove(attributeName)
147 | }
148 |
149 | override fun render(builder: Appendable, indent: String, printOptions: PrintOptions) {
150 | val lineEnding = getLineEnding(printOptions)
151 | builder.append("$indent<$nodeName${renderNamespaces()}${renderAttributes(printOptions)}")
152 |
153 | if (!isEmptyOrSingleEmptyTextElement()) {
154 | if (printOptions.pretty && printOptions.singleLineTextElements &&
155 | _children.size == 1 && _children[0] is TextElement
156 | ) {
157 | builder.append(">")
158 | (_children[0] as TextElement).renderSingleLine(builder, printOptions)
159 | builder.append("$nodeName>$lineEnding")
160 | } else {
161 | builder.append(">$lineEnding")
162 | for (c in sortedChildren()) {
163 | c.render(builder, getIndent(printOptions, indent), printOptions)
164 | }
165 |
166 | builder.append("$indent$nodeName>$lineEnding")
167 | }
168 | } else {
169 | builder.append("${getEmptyTagClosing(printOptions)}$lineEnding")
170 | }
171 | }
172 |
173 | private fun isEmptyOrSingleEmptyTextElement(): Boolean {
174 | if (_children.isEmpty()) {
175 | return true
176 | }
177 |
178 | if (_children.size == 1 && _children[0] is TextElement) {
179 | return (_children[0] as TextElement).text.isEmpty()
180 | }
181 |
182 | return false
183 | }
184 |
185 | private fun getEmptyTagClosing(printOptions: PrintOptions): String = if (printOptions.useSelfClosingTags) {
186 | "/>"
187 | } else {
188 | ">$nodeName>"
189 | }
190 |
191 | private fun sortedChildren(): List {
192 | return if (childOrderMap == null) {
193 | _children
194 | } else {
195 | _children.sortedWith { a, b ->
196 | val indexA = if (a is Node) childOrderMap!![a.nodeName] else 0
197 | val indexB = if (b is Node) childOrderMap!![b.nodeName] else 0
198 |
199 | compareValues(indexA, indexB)
200 | }
201 | }
202 | }
203 |
204 | private fun renderNamespaces(): String {
205 | if (namespaces.isEmpty()) {
206 | return ""
207 | }
208 |
209 | val parentNamespaces = getParentNamespaces()
210 | val namespacesNeeded = namespaces
211 | .filterNot { parentNamespaces.contains(it) }
212 |
213 | if (namespacesNeeded.isEmpty()) {
214 | return ""
215 | }
216 |
217 | return namespacesNeeded.joinToString(" ", prefix = " ")
218 | }
219 |
220 | private fun renderAttributes(printOptions: PrintOptions): String {
221 | if (_attributes.isEmpty()) {
222 | return ""
223 | }
224 |
225 | return _attributes.entries.joinToString(" ", prefix = " ") {
226 | val value = it.value
227 | val text = if (value is Unsafe) {
228 | value.value?.toString()
229 | } else {
230 | escapeValue(
231 | it.value,
232 | printOptions.xmlVersion,
233 | printOptions.useCharacterReference
234 | )
235 | }
236 |
237 | "${it.key}=\"$text\""
238 | }
239 | }
240 |
241 | private fun getIndent(printOptions: PrintOptions, indent: String): String =
242 | if (!printOptions.pretty) { "" } else { "$indent${printOptions.indent}" }
243 |
244 | /**
245 | * Get the XML representation of this object with `prettyFormat = true`.
246 | */
247 | override fun toString() = toString(prettyFormat = true)
248 |
249 | /**
250 | * Get the XML representation of this object.
251 | *
252 | * @param [prettyFormat] true to format the XML with newlines and tabs; otherwise no formatting
253 | */
254 | fun toString(prettyFormat: Boolean): String = toString(PrintOptions(pretty = prettyFormat))
255 |
256 | fun toString(printOptions: PrintOptions): String =
257 | StringBuilder().also { writeTo(it, printOptions) }.toString().trim()
258 |
259 | fun writeTo(appendable: Appendable, printOptions: PrintOptions = PrintOptions()) {
260 | val lineEnding = getLineEnding(printOptions)
261 |
262 | printOptions.xmlVersion = version
263 |
264 | if (includeXmlProlog) {
265 | appendable.append("$lineEnding")
272 | }
273 |
274 | doctype?.apply {
275 | render(appendable, "", printOptions)
276 | }
277 |
278 | if (_globalLevelProcessingInstructions.isNotEmpty()) {
279 | _globalLevelProcessingInstructions.forEach { it.render(appendable, "", printOptions) }
280 | }
281 |
282 | render(appendable, "", printOptions)
283 | }
284 |
285 | operator fun String.unaryMinus() = text(this)
286 |
287 | fun unsafeText(text: String) {
288 | _children.add(TextElement(text, unsafe = true))
289 | }
290 |
291 | fun text(text: String) {
292 | _children.add(TextElement(text))
293 | }
294 |
295 | /**
296 | * Adds an XML comment to the document.
297 | * ```
298 | * comment("my comment")
299 | * ```
300 | *
301 | * @param text The text of the comment. This text will be rendered unescaped except for replace `--` with `--`
302 | */
303 | fun comment(text: String) {
304 | _children.add(Comment(text))
305 | }
306 |
307 | /**
308 | * Adds a basic element with the specific name to the parent.
309 | * ```
310 | * element("url") {
311 | * // ...
312 | * }
313 | * ```
314 | *
315 | * @param name The name of the element.
316 | * @param namespace Optional namespace object to use to build the name of the attribute.
317 | * @param init The block that defines the content of the element.
318 | */
319 | fun element(name: String, namespace: Namespace? = null, init: (Node.() -> Unit)? = null): Node {
320 | val node = Node(buildName(name, namespace))
321 | if (namespace != null) {
322 | node.addNamespace(namespace)
323 | }
324 | initTag(node, init)
325 | return node
326 | }
327 |
328 | /**
329 | * Adds a basic element with the specific name and value to the parent. This cannot be used for complex elements.
330 | * ```
331 | * element("url", "https://google.com")
332 | * ```
333 | *
334 | * @param name The name of the element.
335 | * @param value The inner text of the element
336 | * @param namespace Optional namespace object to use to build the name of the attribute.
337 | */
338 | fun element(name: String, value: String, namespace: Namespace? = null): Node {
339 | val node = Node(buildName(name, namespace))
340 | if (namespace != null) {
341 | node.addNamespace(namespace)
342 | }
343 |
344 | initTag(node) {
345 | -value
346 | }
347 | return node
348 | }
349 |
350 | /**
351 | * Adds a basic element with the specific name and value to the parent. This cannot be used for complex elements.
352 | * ```
353 | * "url"("https://google.com")
354 | * ```
355 | *
356 | * @receiver The name of the element.
357 | * @param value The inner text of the element
358 | * @param namespace Optional namespace object to use to build the name of the attribute.
359 | */
360 | operator fun String.invoke(value: String, namespace: Namespace? = null): Node = element(this, value, namespace)
361 |
362 | /**
363 | * Adds a basic element with the specific name to the parent. This method
364 | * allows you to specify optional attributes and content
365 | * ```
366 | * "url"("key" to "value") {
367 | * // ...
368 | * }
369 | * ```
370 | *
371 | * @receiver The name of the element.
372 | * @param attributes Any attributes to add to this element. Can be omitted.
373 | * @param init The block that defines the content of the element.
374 | */
375 | operator fun String.invoke(vararg attributes: Pair, init: (Node.() -> Unit)? = null): Node {
376 | return addElement(this, attributes.map { Attribute(it.first, it.second) }.toTypedArray(), null, init)
377 | }
378 |
379 | /**
380 | * Adds a basic element with the specific name to the parent. This method
381 | * allows you to specify the namespace of the element as well as optional attributes and content
382 | * ```
383 | * "url"(ns, "key" to "value") {
384 | * // ...
385 | * }
386 | * ```
387 | *
388 | * @receiver The name of the element.
389 | * @param namespace Namespace object to use to build the name of the attribute.
390 | * @param attributes Any attributes to add to this element. Can be omitted.
391 | * @param init The block that defines the content of the element.
392 | */
393 | operator fun String.invoke(
394 | namespace: Namespace,
395 | vararg attributes: Pair,
396 | init: (Node.() -> Unit)? = null
397 | ): Node {
398 | return addElement(this, attributes.map { Attribute(it.first, it.second) }.toTypedArray(), namespace, init)
399 | }
400 |
401 | /**
402 | * Adds a basic element with the specific name to the parent. This method
403 | * allows you to specify the namespace of the element
404 | * ```
405 | * "url"(ns) {
406 | * ...
407 | * }
408 | * ```
409 | *
410 | * @receiver The name of the element.
411 | * @param namespace Namespace object to use to build the name of the attribute.
412 | * @param init The block that defines the content of the element.
413 | */
414 | operator fun String.invoke(namespace: Namespace, init: (Node.() -> Unit)? = null): Node {
415 | return addElement(this, emptyArray(), namespace, init)
416 | }
417 |
418 | /**
419 | * Adds a basic element with the specific name to the parent. This method
420 | * allows you to specify namespace of the element as well as optional attributes (with namespaces) and content
421 | * ```
422 | * "url"(ns, Attribute("key", "value", otherNs)) {
423 | * ...
424 | * }
425 | * ```
426 | *
427 | * @receiver The name of the element.
428 | * @param namespace Namespace object to use to build the name of the attribute.
429 | * @param attributes Any attributes to add to this element. Can be omitted.
430 | * @param init The block that defines the content of the element.
431 | */
432 | operator fun String.invoke(
433 | namespace: Namespace,
434 | vararg attributes: Attribute,
435 | init: (Node.() -> Unit)? = null
436 | ): Node {
437 | return addElement(this, attributes, namespace, init)
438 | }
439 |
440 | private fun addElement(
441 | name: String,
442 | attributes: Array,
443 | namespace: Namespace?,
444 | init: (Node.() -> Unit)?
445 | ): Node {
446 | val e = element(name, namespace) {
447 | attributes(*attributes)
448 | }
449 |
450 | if (init != null) {
451 | e.apply(init)
452 | }
453 |
454 | return e
455 | }
456 |
457 | private fun addNamespace(namespace: Namespace) {
458 | if (namespace.isDefault) {
459 | _namespaces.removeIf(Namespace::isDefault)
460 | }
461 |
462 | _namespaces.add(namespace)
463 | }
464 |
465 | /**
466 | * Adds an attribute to the current element
467 | * ```
468 | * "url" {
469 | * attribute("key", "value")
470 | * }
471 | * ```
472 | *
473 | * @param name The name of the attribute. This is currently no validation against the name.
474 | * @param value The attribute value.
475 | * @param namespace Optional namespace object to use to build the name of the attribute. Note this does NOT declare
476 | * the namespace. It simply uses it to build the attribute name.
477 | */
478 | fun attribute(name: String, value: Any, namespace: Namespace? = null) {
479 | if (namespace != null) {
480 | addNamespace(namespace)
481 | }
482 | _attributes[buildName(name, namespace)] = value
483 | }
484 |
485 | /**
486 | * Adds a set of attributes to the current element.
487 | * ```
488 | * "url" {
489 | * attributes(
490 | * "key" to "value",
491 | * "id" to "1"
492 | * )
493 | * }
494 | * ```
495 | * @param attrs Collection of the attributes to apply to this element.
496 | * @see attribute
497 | */
498 | fun attributes(vararg attrs: Pair) {
499 | attrs.forEach { attribute(it.first, it.second) }
500 | }
501 |
502 | /**
503 | * Adds a set of attributes to the current element.
504 | * ```
505 | * "url" {
506 | * attributes(
507 | * Attribute("key", "value", namespace),
508 | * Attribute("id", "1", namespace)
509 | * )
510 | * }
511 | * ```
512 | *
513 | * @param attrs Collection of the attributes to apply to this element.
514 | * @see attribute
515 | */
516 | fun attributes(vararg attrs: Attribute) {
517 | for (attr in attrs) {
518 | if (attr.namespace != null) {
519 | addNamespace(attr.namespace)
520 | }
521 | attribute(buildName(attr.name, attr.namespace), attr.value)
522 | }
523 | }
524 |
525 | /**
526 | * Adds a set of attributes to the current element.
527 | *
528 | * ```
529 | * "url" {
530 | * attributes(
531 | * "key" to "value",
532 | * "id" to "1"
533 | * )
534 | * }
535 | * ```
536 | *
537 | * @param namespace Optional namespace object to use to build the name of the attribute. Note this does NOT declare
538 | * the namespace. It simply uses it to build the attribute name(s).
539 | * @param attrs Collection of the attributes to apply to this element.
540 | * @see attribute
541 | */
542 | fun attributes(namespace: Namespace, vararg attrs: Pair) {
543 | attrs.forEach { attribute(it.first, it.second, namespace) }
544 | }
545 |
546 | /**
547 | * Adds the supplied text as a CDATA element
548 | *
549 | * @param text The inner text of the CDATA element.
550 | */
551 | fun cdata(text: String) {
552 | _children.add(CDATAElement(text))
553 | }
554 |
555 | /**
556 | * Adds the supplied text as a processing instruction element
557 | *
558 | * @param text The inner text of the processing instruction element.
559 | * @param attributes Optional set of attributes to apply to this processing instruction.
560 | */
561 | fun processingInstruction(text: String, vararg attributes: Pair) {
562 | _children.add(ProcessingInstructionElement(text, linkedMapOf(*attributes)))
563 | }
564 |
565 | /**
566 | * Adds the supplied text as a processing instruction element to the root of the document.
567 | *
568 | * @param text The inner text of the processing instruction element.
569 | * @param attributes Optional set of attributes to apply to this processing instruction.
570 | */
571 | fun globalProcessingInstruction(text: String, vararg attributes: Pair) {
572 | _globalLevelProcessingInstructions.add(ProcessingInstructionElement(text, linkedMapOf(*attributes)))
573 | }
574 |
575 | /**
576 | * Add a DTD to the document.
577 | *
578 | * @param name The name of the DTD element. Not supplying this or passing null will default to [nodeName].
579 | * @param publicId The public declaration of the DTD.
580 | * @param systemId The system declaration of the DTD.
581 | */
582 | fun doctype(name: String? = null, publicId: String? = null, systemId: String? = null) {
583 | if (publicId != null && systemId == null) {
584 | throw IllegalStateException("systemId must be provided if publicId is provided")
585 | }
586 |
587 | doctype = Doctype(name ?: nodeName, publicId = publicId, systemId = systemId)
588 | }
589 |
590 | /**
591 | * Adds the specified namespace to the element.
592 | * ```
593 | * "url" {
594 | * namespace("t", "http://someurl.org")
595 | * }
596 | * ```
597 | *
598 | * @param name The name of the namespace.
599 | * @param value The url or descriptor of the namespace.
600 | */
601 | fun namespace(name: String, value: String): Namespace {
602 | val ns = Namespace(name, value)
603 | namespace(ns)
604 | return ns
605 | }
606 |
607 | /**
608 | * Adds the specified namespace to the element.
609 | * ```
610 | * val ns = Namespace("t", "http://someurl.org")
611 | * "url" {
612 | * namespace(ns)
613 | * }
614 | * ```
615 | *
616 | * @param namespace The namespace object to use for the element's namespace declaration.
617 | */
618 | fun namespace(namespace: Namespace) {
619 | addNamespace(namespace)
620 | }
621 |
622 | /**
623 | * Adds a node to the node.
624 | * @param node The node to append.
625 | */
626 | @Deprecated(
627 | message = "Use addElement instead",
628 | replaceWith = ReplaceWith("addElement(node)")
629 | )
630 | fun addNode(node: Node) = addElement(node)
631 |
632 | /**
633 | * Adds a element to the node.
634 | * @param element The element to append.
635 | */
636 | fun addElement(element: Element) {
637 | setParentIfNode(element, this)
638 | _children.add(element)
639 | }
640 |
641 | /**
642 | * Adds the provided elements to the node.
643 | * @param elements The elements to append.
644 | */
645 | fun addElements(vararg elements: Element) = elements.forEach { addElement(it) }
646 |
647 | /**
648 | * Adds the provided elements to the node.
649 | * @param elements The elements to append.
650 | */
651 | fun addElements(elements: Iterable) = elements.forEach { addElement(it) }
652 |
653 | /**
654 | * Adds a node to the node after the specific node.
655 | * @param node The node to add
656 | * @param after The node to add [node] after
657 | *
658 | * @throws IllegalArgumentException If [after] can't be found
659 | */
660 | @Deprecated(
661 | message = "Use addElementAfter instead",
662 | replaceWith = ReplaceWith("addElementAfter(node, after)")
663 | )
664 | fun addNodeAfter(node: Node, after: Node) = addElementAfter(node, after)
665 |
666 | /**
667 | * Adds an element to the node after the specific element.
668 | * @param element The element to add
669 | * @param after The element to add [element] after
670 | *
671 | * @throws IllegalArgumentException If [after] can't be found
672 | */
673 | fun addElementAfter(element: Element, after: Element) {
674 | val index = findIndex(after)
675 |
676 | setParentIfNode(element, this)
677 |
678 | if (index + 1 == _children.size) {
679 | _children.add(element)
680 | } else {
681 | _children.add(index + 1, element)
682 | }
683 | }
684 |
685 | /**
686 | * Adds elements to the node after the specific element.
687 | * @param elements The elements to add
688 | * @param after The element to add [elements] after
689 | *
690 | * @throws IllegalArgumentException If [after] can't be found
691 | */
692 | fun addElementsAfter(elements: Iterable, after: Element) {
693 | val index = findIndex(after) + 1
694 |
695 | if (index == _children.size) {
696 | addElements(elements)
697 | } else {
698 | val firstPart = _children.take(index)
699 | val lastPart = _children.drop(index)
700 | _children.clear()
701 | _children.addAll(firstPart)
702 | addElements(elements)
703 | _children.addAll(lastPart)
704 | }
705 | }
706 |
707 | /**
708 | * Adds elements to the node after the specific element.
709 | * @param after The element to add [elements] after
710 | * @param elements The elements to add
711 | *
712 | * @throws IllegalArgumentException If [after] can't be found
713 | */
714 | fun addElementsAfter(after: Element, vararg elements: Element) =
715 | addElementsAfter(listOf(*elements), after)
716 |
717 | /**
718 | * Adds a node to the node before the specific node.
719 | * @param node The node to add
720 | * @param before The node to add [node] before
721 | *
722 | * @throws IllegalArgumentException If [before] can't be found
723 | */
724 | @Deprecated(
725 | message = "Use addElementBefore instead",
726 | replaceWith = ReplaceWith("addElementBefore(node, before)")
727 | )
728 | fun addNodeBefore(node: Node, before: Node) = addElementBefore(node, before)
729 |
730 | /**
731 | * Adds an element to the node before the specific element.
732 | * @param element The element to add
733 | * @param before The element to add [element] before
734 | *
735 | * @throws IllegalArgumentException If [before] can't be found
736 | */
737 | fun addElementBefore(element: Element, before: Element) {
738 | val index = findIndex(before)
739 | setParentIfNode(element, this)
740 | _children.add(index, element)
741 | }
742 |
743 | /**
744 | * Adds elements to the node before the specific element.
745 | * @param elements The elements to add
746 | * @param before The element to add [elements] before
747 | *
748 | * @throws IllegalArgumentException If [before] can't be found
749 | */
750 | fun addElementsBefore(elements: Iterable, before: Element) {
751 | val index = findIndex(before)
752 | val firstPart = _children.take(index)
753 | val lastPart = _children.drop(index)
754 | _children.clear()
755 | _children.addAll(firstPart)
756 | addElements(elements)
757 | _children.addAll(lastPart)
758 | }
759 |
760 | /**
761 | * Adds elements to the node before the specific element.
762 | * @param before The element to add [elements] before
763 | * @param elements The elements to add
764 | *
765 | * @throws IllegalArgumentException If [before] can't be found
766 | */
767 | fun addElementsBefore(before: Element, vararg elements: Element) =
768 | addElementsBefore(listOf(*elements), before)
769 |
770 | /**
771 | * Removes a node from the node.
772 | * @param node The node to remove
773 | *
774 | * @throws IllegalArgumentException If [node] can't be found
775 | */
776 | @Deprecated(
777 | message = "Use removeElement instead",
778 | replaceWith = ReplaceWith("removeElement(node)")
779 | )
780 | fun removeNode(node: Node) = removeElement(node)
781 |
782 | /**
783 | * Removes an element from the node.
784 | * @param element The element to remove
785 | *
786 | * @throws IllegalArgumentException If [element] can't be found
787 | */
788 | fun removeElement(element: Element) {
789 | val index = findIndex(element)
790 | removeChildAt(index)
791 | }
792 |
793 | /**
794 | * Removes the elements from the node.
795 | * @param elements The elements to remove
796 | *
797 | * @throws IllegalArgumentException If any [elements] can't be found
798 | */
799 | fun removeElements(vararg elements: Element) = removeElements(listOf(*elements))
800 |
801 | /**
802 | * Removes the elements from the node.
803 | * @param elements The elements to remove
804 | *
805 | * @throws IllegalArgumentException If any [elements] can't be found
806 | */
807 | fun removeElements(elements: Iterable) =
808 | elements
809 | .map { findIndex(it) }
810 | .sortedDescending()
811 | .forEach { removeChildAt(it) }
812 |
813 | private fun removeChildAt(index: Int) {
814 | val child = _children.removeAt(index)
815 | setParentIfNode(child, null)
816 | }
817 |
818 | /**
819 | * Replaces a node with a different node.
820 | * @param existing The existing node to replace
821 | * @param newNode The node to replace [existing] with
822 | *
823 | * @throws IllegalArgumentException If [existing] can't be found
824 | */
825 | @Deprecated(
826 | message = "Use replaceElement instead",
827 | replaceWith = ReplaceWith("replaceElement(exising, newNode)")
828 | )
829 | fun replaceNode(existing: Node, newNode: Node) = replaceElement(existing, newNode)
830 |
831 | /**
832 | * Replaces an element with a different element.
833 | * @param existing The existing element to replace
834 | * @param newElement The element to replace [existing] with
835 | *
836 | * @throws IllegalArgumentException If [existing] can't be found
837 | */
838 | fun replaceElement(existing: Element, newElement: Element) {
839 | val index = findIndex(existing)
840 |
841 | setParentIfNode(newElement, this)
842 | setParentIfNode(existing, null)
843 |
844 | _children[index] = newElement
845 | }
846 |
847 | /**
848 | * Returns a list containing only elements whose nodeName matches [name].
849 | */
850 | fun filter(name: String): List = filter { it.nodeName == name }
851 |
852 | /**
853 | * Returns a list containing only elements matching the given [predicate].
854 | */
855 | fun filter(predicate: (Node) -> Boolean): List = filterChildrenToNodes().filter(predicate)
856 |
857 | /**
858 | * Returns the first element whose nodeName matches [name].
859 | * @throws [NoSuchElementException] if no such element is found.
860 | */
861 | fun first(name: String): Node = filterChildrenToNodes().first { it.nodeName == name }
862 |
863 | /**
864 | * Returns the first element matching the given [predicate].
865 | * @throws [NoSuchElementException] if no such element is found.
866 | */
867 | fun first(predicate: (Element) -> Boolean): Element = _children.first(predicate)
868 |
869 | /**
870 | * Returns the first element whose nodeName matches [name], or `null` if element was not found.
871 | */
872 | fun firstOrNull(name: String): Node? = filterChildrenToNodes().firstOrNull { it.nodeName == name }
873 |
874 | /**
875 | * Returns the first element matching the given [predicate], or `null` if element was not found.
876 | */
877 | fun firstOrNull(predicate: (Element) -> Boolean): Element? = _children.firstOrNull(predicate)
878 |
879 | /**
880 | * Returns `true` if at least one element's nodeName matches [name].
881 | */
882 | fun exists(name: String): Boolean = filterChildrenToNodes().any { it.nodeName == name }
883 |
884 | /**
885 | * Returns `true` if at least one element matches the given [predicate].
886 | */
887 | fun exists(predicate: (Element) -> Boolean): Boolean = _children.any(predicate)
888 |
889 | private fun filterChildrenToNodes(): List = _children.filterIsInstance(Node::class.java)
890 |
891 | private fun findIndex(element: Element): Int {
892 | return _children
893 | .indexOfFirst { it === element }
894 | .takeUnless { it == -1 }
895 | ?: throw IllegalArgumentException("Element (${element.javaClass} is not a child of '$nodeName'")
896 | }
897 |
898 | private fun setParentIfNode(element: Element, newParent: Node?) {
899 | if (element is Node) {
900 | element.parent = newParent
901 | }
902 | }
903 |
904 | override fun equals(other: Any?): Boolean {
905 | if (other !is Node) {
906 | return false
907 | }
908 |
909 | return EqualsBuilder()
910 | .append(nodeName, other.nodeName)
911 | .append(encoding, other.encoding)
912 | .append(version, other.version)
913 | .append(_attributes, other._attributes)
914 | .append(_globalLevelProcessingInstructions, other._globalLevelProcessingInstructions)
915 | .append(_children, other._children)
916 | .isEquals
917 | }
918 |
919 | override fun hashCode(): Int = HashCodeBuilder()
920 | .append(nodeName)
921 | .append(encoding)
922 | .append(version)
923 | .append(_attributes)
924 | .append(_globalLevelProcessingInstructions)
925 | .append(_children)
926 | .toHashCode()
927 | }
928 |
--------------------------------------------------------------------------------