├── .editorconfig ├── .github ├── dependabot.yaml └── workflows │ └── build.yaml ├── .gitignore ├── LICENSE.txt ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotlin-js-store └── yarn.lock ├── settings.gradle.kts └── src ├── commonMain └── kotlin │ └── com │ └── ryanharter │ └── kotlinx │ └── serialization │ └── xml │ ├── Xml.kt │ ├── XmlDecoder.kt │ ├── XmlEncoder.kt │ ├── XmlEntities.kt │ ├── annotations.kt │ ├── errors.kt │ └── internal │ ├── Composer.kt │ ├── StreamingXmlDecoder.kt │ ├── StreamingXmlEncoder.kt │ ├── XmlEntitySerializer.kt │ └── XmlLexer.kt └── commonTest └── kotlin └── com └── ryanharter └── kotlinx └── serialization └── xml ├── XmlDecoderTest.kt ├── XmlEncoderTest.kt └── XmlLexerTest.kt /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "gradle" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - '**' 8 | tags-ignore: 9 | - '**' 10 | 11 | env: 12 | GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dkotlin.incremental=false" 13 | 14 | jobs: 15 | build: 16 | # We run on a Mac because it can cross-compile the Kotlin/Native Linux and Windows binaries 17 | # while also being required for the macOS/iOS/watchOS/tvOS binaries. 18 | runs-on: macos-11 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: gradle/wrapper-validation-action@v3 23 | - uses: actions/setup-java@v4 24 | with: 25 | distribution: 'zulu' 26 | java-version: 18 27 | 28 | - run: ./gradlew build 29 | 30 | - run: ./gradlew publish 31 | if: ${{ github.ref == 'refs/heads/main' && github.repository == 'rharter/kotlinx-serialization-xml' }} 32 | env: 33 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} 34 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .gradle 3 | .project 4 | .settings 5 | eclipsebin 6 | 7 | bin 8 | gen 9 | build 10 | out 11 | lib 12 | reports 13 | 14 | .idea 15 | *.iml 16 | *.ipr 17 | *.iws 18 | classes 19 | local.properties 20 | 21 | obj 22 | 23 | .DS_Store 24 | 25 | node_modules 26 | 27 | # Special Mkdocs files 28 | docs/3.x 29 | docs/changelog.md 30 | docs/contributing.md 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Serialization XML 2 | 3 | A fully native, multiplatform XML format add-on for [Kotlin Serialization](https://github.com/Kotlin/kotlinx.serialization). 4 | 5 | > **Status**: This project is in the early stages of development, so isn't yet feature complete. 6 | 7 | ## Usage 8 | 9 | Kotlin Serialization XML provides an `Xml` format for Kotlin Serialization, allowing you to use the 10 | standard `@Serializable` annotation to create reflectionless, multiplatform serializers for your Kotlin 11 | classes. 12 | 13 | ```kotlin 14 | @Serializable 15 | data class Greeting( 16 | @XmlAttribute val from: String, 17 | @XmlAttribute val to: String, 18 | val message: Message 19 | ) 20 | 21 | @Serializable 22 | data class Message( 23 | @XmlContent val content: String 24 | ) 25 | 26 | val xml = """ 27 | 28 | Hi 29 | 30 | """.trimIndent() 31 | val actual = Xml.Default.decodeFromString(xml) 32 | ``` 33 | 34 | By default, all properties are encoded as nested XML elements. 35 | 36 | To encode properties as attributes, they must be annotated with `@XmlAttribute`. All `@XmlAttribute` 37 | annotated properties must come before other properties in an object. 38 | 39 | After the attributes, properties will be encoded as nested elements, but can also be encoded as 40 | bare string content by annotating the property with `@XmlContent`. 41 | 42 | ## Installation 43 | 44 | Development versions are available in Sonatype's snapshot repository. 45 | 46 | ```kotlin 47 | repositories { 48 | maven { 49 | url("https://oss.sonatype.org/content/repositories/snapshots/") 50 | } 51 | } 52 | dependencies { 53 | implementation("com.ryanharter.kotlinx.serialization:kotlinx-serialization-xml:0.0.1-SNAPSHOT") 54 | } 55 | ``` 56 | 57 | # License 58 | 59 | Copyright 2022 Ryan Harter 60 | 61 | Licensed under the Apache License, Version 2.0 (the "License"); 62 | you may not use this file except in compliance with the License. 63 | You may obtain a copy of the License at 64 | 65 | http://www.apache.org/licenses/LICENSE-2.0 66 | 67 | Unless required by applicable law or agreed to in writing, software 68 | distributed under the License is distributed on an "AS IS" BASIS, 69 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 70 | See the License for the specific language governing permissions and 71 | limitations under the License. 72 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.vanniktech.maven.publish.KotlinMultiplatform 2 | import com.vanniktech.maven.publish.SonatypeHost.DEFAULT 3 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests 4 | import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType 5 | import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable 6 | 7 | plugins { 8 | alias(libs.plugins.kotlin.multiplatform) 9 | alias(libs.plugins.kotlin.serialization) 10 | alias(libs.plugins.mavenPublish) 11 | } 12 | 13 | group = "com.ryanharter.kotlinx.serialization" 14 | version = "0.0.1-SNAPSHOT" 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | kotlin { 21 | explicitApi() 22 | 23 | jvm { 24 | compilations.all { 25 | kotlinOptions.jvmTarget = "1.8" 26 | } 27 | withJava() 28 | testRuns["test"].executionTask.configure { 29 | useJUnitPlatform() 30 | } 31 | } 32 | js { 33 | nodejs() 34 | } 35 | 36 | // Note: Keep native list in sync with kotlinx.serialization: 37 | // https://github.com/Kotlin/kotlinx.serialization/blob/master/gradle/native-targets.gradle 38 | linuxX64() 39 | linuxArm32Hfp() 40 | linuxArm64() 41 | macosX64() 42 | macosArm64() 43 | mingwX86() 44 | mingwX64() 45 | iosX64() 46 | iosArm32() 47 | iosArm64() 48 | iosSimulatorArm64() 49 | watchosX86() 50 | watchosX64() 51 | watchosArm32() 52 | watchosArm64() 53 | watchosSimulatorArm64() 54 | tvosX64() 55 | tvosArm64() 56 | tvosSimulatorArm64() 57 | 58 | sourceSets { 59 | val commonMain by getting { 60 | dependencies { 61 | api(libs.kotlinx.serialization) 62 | } 63 | } 64 | val commonTest by getting { 65 | dependencies { 66 | implementation(kotlin("test")) 67 | } 68 | } 69 | } 70 | 71 | sourceSets.all { 72 | languageSettings { 73 | optIn("kotlinx.serialization.ExperimentalSerializationApi") 74 | } 75 | } 76 | 77 | // Add a test binary and execution for native targets which runs on a background thread. 78 | targets.withType(KotlinNativeTargetWithTests::class).all { 79 | binaries { 80 | test("background", listOf(NativeBuildType.DEBUG)) { 81 | freeCompilerArgs += listOf("-trw") 82 | } 83 | } 84 | testRuns { 85 | create("background") { 86 | setExecutionSourceFrom(binaries.getByName("backgroundDebugTest") as TestExecutable) 87 | } 88 | } 89 | } 90 | } 91 | 92 | mavenPublishing { 93 | configure(KotlinMultiplatform()) 94 | 95 | publishToMavenCentral(DEFAULT) 96 | signAllPublications() 97 | pom { 98 | description.set("A fully native, multiplatform XML format add-on for Kotlin Serialization.") 99 | name.set(project.name) 100 | url.set("https://github.com/rharter/kotlinx-serialization-xml/") 101 | licenses { 102 | license { 103 | name.set("The Apache Software License, Version 2.0") 104 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 105 | distribution.set("repo") 106 | } 107 | } 108 | scm { 109 | url.set("https://github.com/rharter/kotlinx-serialization-xml/") 110 | connection.set("scm:git:git://github.com/rharter/kotlinx-serialization-xml.git") 111 | developerConnection.set("scm:git:ssh://git@github.com/rharter/kotlinx-serialization-xml.git") 112 | } 113 | developers { 114 | developer { 115 | id.set("rharter") 116 | name.set("Ryan Harter") 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.js.generate.executable.default=false 3 | kotlin.js.compiler=ir 4 | kotlin.mpp.stability.nowarn=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.8.10" 3 | 4 | [plugins] 5 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 6 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 7 | mavenPublish = { id = "com.vanniktech.maven.publish.base", version = "0.22.0" } 8 | 9 | [libraries] 10 | kotlinx-serialization = "org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1" 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rharter/kotlinx-serialization-xml/57c47f0a84f8e94c68985a32650788ce7e9fb7a2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /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 Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /kotlin-js-store/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@ungap/promise-all-settled@1.1.2": 6 | version "1.1.2" 7 | resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" 8 | integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== 9 | 10 | ansi-colors@4.1.1: 11 | version "4.1.1" 12 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" 13 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== 14 | 15 | ansi-regex@^5.0.1: 16 | version "5.0.1" 17 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 18 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 19 | 20 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 21 | version "4.3.0" 22 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 23 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 24 | dependencies: 25 | color-convert "^2.0.1" 26 | 27 | anymatch@~3.1.2: 28 | version "3.1.2" 29 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 30 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 31 | dependencies: 32 | normalize-path "^3.0.0" 33 | picomatch "^2.0.4" 34 | 35 | argparse@^2.0.1: 36 | version "2.0.1" 37 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 38 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 39 | 40 | balanced-match@^1.0.0: 41 | version "1.0.2" 42 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 43 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 44 | 45 | binary-extensions@^2.0.0: 46 | version "2.2.0" 47 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 48 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 49 | 50 | brace-expansion@^1.1.7: 51 | version "1.1.11" 52 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 53 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 54 | dependencies: 55 | balanced-match "^1.0.0" 56 | concat-map "0.0.1" 57 | 58 | brace-expansion@^2.0.1: 59 | version "2.0.1" 60 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" 61 | integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== 62 | dependencies: 63 | balanced-match "^1.0.0" 64 | 65 | braces@~3.0.2: 66 | version "3.0.2" 67 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 68 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 69 | dependencies: 70 | fill-range "^7.0.1" 71 | 72 | browser-stdout@1.3.1: 73 | version "1.3.1" 74 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 75 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 76 | 77 | buffer-from@^1.0.0: 78 | version "1.1.2" 79 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 80 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 81 | 82 | camelcase@^6.0.0: 83 | version "6.3.0" 84 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" 85 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== 86 | 87 | chalk@^4.1.0: 88 | version "4.1.2" 89 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 90 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 91 | dependencies: 92 | ansi-styles "^4.1.0" 93 | supports-color "^7.1.0" 94 | 95 | chokidar@3.5.3: 96 | version "3.5.3" 97 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 98 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 99 | dependencies: 100 | anymatch "~3.1.2" 101 | braces "~3.0.2" 102 | glob-parent "~5.1.2" 103 | is-binary-path "~2.1.0" 104 | is-glob "~4.0.1" 105 | normalize-path "~3.0.0" 106 | readdirp "~3.6.0" 107 | optionalDependencies: 108 | fsevents "~2.3.2" 109 | 110 | cliui@^7.0.2: 111 | version "7.0.4" 112 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 113 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 114 | dependencies: 115 | string-width "^4.2.0" 116 | strip-ansi "^6.0.0" 117 | wrap-ansi "^7.0.0" 118 | 119 | color-convert@^2.0.1: 120 | version "2.0.1" 121 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 122 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 123 | dependencies: 124 | color-name "~1.1.4" 125 | 126 | color-name@~1.1.4: 127 | version "1.1.4" 128 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 129 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 130 | 131 | concat-map@0.0.1: 132 | version "0.0.1" 133 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 134 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 135 | 136 | debug@4.3.4: 137 | version "4.3.4" 138 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 139 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 140 | dependencies: 141 | ms "2.1.2" 142 | 143 | decamelize@^4.0.0: 144 | version "4.0.0" 145 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" 146 | integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== 147 | 148 | diff@5.0.0: 149 | version "5.0.0" 150 | resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" 151 | integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== 152 | 153 | emoji-regex@^8.0.0: 154 | version "8.0.0" 155 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 156 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 157 | 158 | escalade@^3.1.1: 159 | version "3.1.1" 160 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 161 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 162 | 163 | escape-string-regexp@4.0.0: 164 | version "4.0.0" 165 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 166 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 167 | 168 | fill-range@^7.0.1: 169 | version "7.0.1" 170 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 171 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 172 | dependencies: 173 | to-regex-range "^5.0.1" 174 | 175 | find-up@5.0.0: 176 | version "5.0.0" 177 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" 178 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 179 | dependencies: 180 | locate-path "^6.0.0" 181 | path-exists "^4.0.0" 182 | 183 | flat@^5.0.2: 184 | version "5.0.2" 185 | resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" 186 | integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== 187 | 188 | format-util@1.0.5: 189 | version "1.0.5" 190 | resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" 191 | integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== 192 | 193 | fs.realpath@^1.0.0: 194 | version "1.0.0" 195 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 196 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 197 | 198 | fsevents@~2.3.2: 199 | version "2.3.2" 200 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 201 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 202 | 203 | get-caller-file@^2.0.5: 204 | version "2.0.5" 205 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 206 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 207 | 208 | glob-parent@~5.1.2: 209 | version "5.1.2" 210 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 211 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 212 | dependencies: 213 | is-glob "^4.0.1" 214 | 215 | glob@7.2.0: 216 | version "7.2.0" 217 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" 218 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== 219 | dependencies: 220 | fs.realpath "^1.0.0" 221 | inflight "^1.0.4" 222 | inherits "2" 223 | minimatch "^3.0.4" 224 | once "^1.3.0" 225 | path-is-absolute "^1.0.0" 226 | 227 | has-flag@^4.0.0: 228 | version "4.0.0" 229 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 230 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 231 | 232 | he@1.2.0: 233 | version "1.2.0" 234 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 235 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 236 | 237 | inflight@^1.0.4: 238 | version "1.0.6" 239 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 240 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 241 | dependencies: 242 | once "^1.3.0" 243 | wrappy "1" 244 | 245 | inherits@2: 246 | version "2.0.4" 247 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 248 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 249 | 250 | is-binary-path@~2.1.0: 251 | version "2.1.0" 252 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 253 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 254 | dependencies: 255 | binary-extensions "^2.0.0" 256 | 257 | is-extglob@^2.1.1: 258 | version "2.1.1" 259 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 260 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 261 | 262 | is-fullwidth-code-point@^3.0.0: 263 | version "3.0.0" 264 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 265 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 266 | 267 | is-glob@^4.0.1, is-glob@~4.0.1: 268 | version "4.0.3" 269 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 270 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 271 | dependencies: 272 | is-extglob "^2.1.1" 273 | 274 | is-number@^7.0.0: 275 | version "7.0.0" 276 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 277 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 278 | 279 | is-plain-obj@^2.1.0: 280 | version "2.1.0" 281 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" 282 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== 283 | 284 | is-unicode-supported@^0.1.0: 285 | version "0.1.0" 286 | resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" 287 | integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== 288 | 289 | js-yaml@4.1.0: 290 | version "4.1.0" 291 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 292 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 293 | dependencies: 294 | argparse "^2.0.1" 295 | 296 | locate-path@^6.0.0: 297 | version "6.0.0" 298 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" 299 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 300 | dependencies: 301 | p-locate "^5.0.0" 302 | 303 | log-symbols@4.1.0: 304 | version "4.1.0" 305 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" 306 | integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== 307 | dependencies: 308 | chalk "^4.1.0" 309 | is-unicode-supported "^0.1.0" 310 | 311 | minimatch@5.0.1: 312 | version "5.0.1" 313 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" 314 | integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== 315 | dependencies: 316 | brace-expansion "^2.0.1" 317 | 318 | minimatch@^3.0.4: 319 | version "3.1.2" 320 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 321 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 322 | dependencies: 323 | brace-expansion "^1.1.7" 324 | 325 | mocha@10.0.0: 326 | version "10.0.0" 327 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" 328 | integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== 329 | dependencies: 330 | "@ungap/promise-all-settled" "1.1.2" 331 | ansi-colors "4.1.1" 332 | browser-stdout "1.3.1" 333 | chokidar "3.5.3" 334 | debug "4.3.4" 335 | diff "5.0.0" 336 | escape-string-regexp "4.0.0" 337 | find-up "5.0.0" 338 | glob "7.2.0" 339 | he "1.2.0" 340 | js-yaml "4.1.0" 341 | log-symbols "4.1.0" 342 | minimatch "5.0.1" 343 | ms "2.1.3" 344 | nanoid "3.3.3" 345 | serialize-javascript "6.0.0" 346 | strip-json-comments "3.1.1" 347 | supports-color "8.1.1" 348 | workerpool "6.2.1" 349 | yargs "16.2.0" 350 | yargs-parser "20.2.4" 351 | yargs-unparser "2.0.0" 352 | 353 | ms@2.1.2: 354 | version "2.1.2" 355 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 356 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 357 | 358 | ms@2.1.3: 359 | version "2.1.3" 360 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 361 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 362 | 363 | nanoid@3.3.3: 364 | version "3.3.3" 365 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" 366 | integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== 367 | 368 | normalize-path@^3.0.0, normalize-path@~3.0.0: 369 | version "3.0.0" 370 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 371 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 372 | 373 | once@^1.3.0: 374 | version "1.4.0" 375 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 376 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 377 | dependencies: 378 | wrappy "1" 379 | 380 | p-limit@^3.0.2: 381 | version "3.1.0" 382 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" 383 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 384 | dependencies: 385 | yocto-queue "^0.1.0" 386 | 387 | p-locate@^5.0.0: 388 | version "5.0.0" 389 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" 390 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 391 | dependencies: 392 | p-limit "^3.0.2" 393 | 394 | path-exists@^4.0.0: 395 | version "4.0.0" 396 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 397 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 398 | 399 | path-is-absolute@^1.0.0: 400 | version "1.0.1" 401 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 402 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 403 | 404 | picomatch@^2.0.4, picomatch@^2.2.1: 405 | version "2.3.1" 406 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 407 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 408 | 409 | randombytes@^2.1.0: 410 | version "2.1.0" 411 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 412 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 413 | dependencies: 414 | safe-buffer "^5.1.0" 415 | 416 | readdirp@~3.6.0: 417 | version "3.6.0" 418 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 419 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 420 | dependencies: 421 | picomatch "^2.2.1" 422 | 423 | require-directory@^2.1.1: 424 | version "2.1.1" 425 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 426 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 427 | 428 | safe-buffer@^5.1.0: 429 | version "5.2.1" 430 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 431 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 432 | 433 | serialize-javascript@6.0.0: 434 | version "6.0.0" 435 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" 436 | integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== 437 | dependencies: 438 | randombytes "^2.1.0" 439 | 440 | source-map-support@0.5.21: 441 | version "0.5.21" 442 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 443 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 444 | dependencies: 445 | buffer-from "^1.0.0" 446 | source-map "^0.6.0" 447 | 448 | source-map@^0.6.0: 449 | version "0.6.1" 450 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 451 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 452 | 453 | string-width@^4.1.0, string-width@^4.2.0: 454 | version "4.2.3" 455 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 456 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 457 | dependencies: 458 | emoji-regex "^8.0.0" 459 | is-fullwidth-code-point "^3.0.0" 460 | strip-ansi "^6.0.1" 461 | 462 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 463 | version "6.0.1" 464 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 465 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 466 | dependencies: 467 | ansi-regex "^5.0.1" 468 | 469 | strip-json-comments@3.1.1: 470 | version "3.1.1" 471 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 472 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 473 | 474 | supports-color@8.1.1: 475 | version "8.1.1" 476 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" 477 | integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== 478 | dependencies: 479 | has-flag "^4.0.0" 480 | 481 | supports-color@^7.1.0: 482 | version "7.2.0" 483 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 484 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 485 | dependencies: 486 | has-flag "^4.0.0" 487 | 488 | to-regex-range@^5.0.1: 489 | version "5.0.1" 490 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 491 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 492 | dependencies: 493 | is-number "^7.0.0" 494 | 495 | workerpool@6.2.1: 496 | version "6.2.1" 497 | resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" 498 | integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== 499 | 500 | wrap-ansi@^7.0.0: 501 | version "7.0.0" 502 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 503 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 504 | dependencies: 505 | ansi-styles "^4.0.0" 506 | string-width "^4.1.0" 507 | strip-ansi "^6.0.0" 508 | 509 | wrappy@1: 510 | version "1.0.2" 511 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 512 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 513 | 514 | y18n@^5.0.5: 515 | version "5.0.8" 516 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 517 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 518 | 519 | yargs-parser@20.2.4: 520 | version "20.2.4" 521 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" 522 | integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== 523 | 524 | yargs-parser@^20.2.2: 525 | version "20.2.9" 526 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" 527 | integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== 528 | 529 | yargs-unparser@2.0.0: 530 | version "2.0.0" 531 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" 532 | integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== 533 | dependencies: 534 | camelcase "^6.0.0" 535 | decamelize "^4.0.0" 536 | flat "^5.0.2" 537 | is-plain-obj "^2.1.0" 538 | 539 | yargs@16.2.0: 540 | version "16.2.0" 541 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" 542 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== 543 | dependencies: 544 | cliui "^7.0.2" 545 | escalade "^3.1.1" 546 | get-caller-file "^2.0.5" 547 | require-directory "^2.1.1" 548 | string-width "^4.2.0" 549 | y18n "^5.0.5" 550 | yargs-parser "^20.2.2" 551 | 552 | yocto-queue@^0.1.0: 553 | version "0.1.0" 554 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 555 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 556 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "kotlinx-serialization-xml" 3 | 4 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/Xml.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | import com.ryanharter.kotlinx.serialization.xml.internal.StreamingXmlDecoder 4 | import com.ryanharter.kotlinx.serialization.xml.internal.StreamingXmlEncoder 5 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlComposer 6 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer 7 | import kotlinx.serialization.DeserializationStrategy 8 | import kotlinx.serialization.ExperimentalSerializationApi 9 | import kotlinx.serialization.SerializationStrategy 10 | import kotlinx.serialization.StringFormat 11 | import kotlinx.serialization.modules.EmptySerializersModule 12 | import kotlinx.serialization.modules.SerializersModule 13 | import kotlin.native.concurrent.ThreadLocal 14 | 15 | public sealed class Xml( 16 | override val serializersModule: SerializersModule, 17 | ) : StringFormat { 18 | 19 | @ExperimentalSerializationApi 20 | @ThreadLocal 21 | public companion object Default : Xml(EmptySerializersModule) 22 | 23 | override fun encodeToString(serializer: SerializationStrategy, value: T): String { 24 | val sb = StringBuilder() 25 | val composer = XmlComposer(sb) 26 | val encoder = StreamingXmlEncoder(this, composer) 27 | serializer.serialize(encoder, value) 28 | return sb.toString() 29 | } 30 | 31 | override fun decodeFromString(deserializer: DeserializationStrategy, string: String): T { 32 | val lexer = XmlLexer(string) 33 | val input = StreamingXmlDecoder(this, lexer) 34 | return input.decodeSerializableValue(deserializer) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/XmlDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | import kotlinx.serialization.encoding.Decoder 4 | 5 | public interface XmlDecoder : Decoder { 6 | public val xml: Xml 7 | public fun decodeXmlEntity(): XmlEntity 8 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/XmlEncoder.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | import kotlinx.serialization.encoding.CompositeEncoder 4 | import kotlinx.serialization.encoding.Encoder 5 | 6 | public interface XmlEncoder : Encoder, CompositeEncoder { 7 | public val xml: Xml 8 | public fun encodeXmlEntity(entity: XmlEntity) 9 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/XmlEntities.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | public sealed interface XmlEntity { 4 | public sealed interface ContentEntity : XmlEntity 5 | 6 | public data class Document( 7 | public val root: Element 8 | ) : XmlEntity 9 | 10 | public data class Value( 11 | public val value: String 12 | ) : ContentEntity 13 | 14 | public data class Element( 15 | public val name: String, 16 | public val namespace: String?, 17 | public val attributes: List = emptyList(), 18 | public val content: List = emptyList(), 19 | ) : ContentEntity 20 | 21 | public data class Attribute( 22 | public val name: String, 23 | public val value: String, 24 | public val prefix: String? = null 25 | ) : XmlEntity 26 | 27 | public data class Comment( 28 | val value: String, 29 | ) : ContentEntity 30 | } -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/annotations.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.SerialInfo 5 | 6 | /** 7 | * Properties annotated with `XmlContent` are serialized as 8 | * text content, instead of elements or attributes. 9 | */ 10 | @ExperimentalSerializationApi 11 | @SerialInfo 12 | @Target(AnnotationTarget.PROPERTY) 13 | public annotation class XmlContent 14 | 15 | @ExperimentalSerializationApi 16 | @SerialInfo 17 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) 18 | public annotation class XmlName(public val name: String = "") 19 | 20 | @ExperimentalSerializationApi 21 | @SerialInfo 22 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) 23 | public annotation class XmlDefaultNamespace( 24 | public val uri: String = "" 25 | ) 26 | 27 | @ExperimentalSerializationApi 28 | @SerialInfo 29 | @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS, AnnotationTarget.TYPE) 30 | @Repeatable 31 | public annotation class XmlNamespace( 32 | /** 33 | * A namespace name, identified by a URI. 34 | */ 35 | public val uri: String = "", 36 | /** 37 | * An optional local name for a namespace. This can be used 38 | * as the prefix in a qualified element or attribute name to 39 | * associate the element or attribute with the namespace. 40 | * 41 | * If no `localName` is provided, this namespace will be used as 42 | * the default namespace when annotating classes, otherwise 43 | * a localName will be generated. 44 | */ 45 | public val localName: String = "", 46 | ) 47 | 48 | /** 49 | * Properties annotated with `XmlAttribute` will be serialized as 50 | * attributes on an XML element. 51 | * 52 | * Properties annotated with `XmlAttribute` will be serialized as 53 | * a string, and must appear first in the serializable object. 54 | */ 55 | @ExperimentalSerializationApi 56 | @SerialInfo 57 | @Target(AnnotationTarget.PROPERTY) 58 | public annotation class XmlAttribute 59 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/errors.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | import kotlinx.serialization.SerializationException 4 | 5 | public open class XmlSerializationException(message: String? = null, cause: Throwable? = null) : 6 | SerializationException(message, cause) 7 | 8 | public class UndefinedNamespaceException 9 | internal constructor(message: String?, cause: Throwable?) : 10 | XmlSerializationException(message, cause) { 11 | public constructor(name: String) : this("Namespace '$name' used, but no definition found.", null) 12 | } 13 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/internal/Composer.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml.internal 2 | 3 | internal interface Composer { 4 | fun indent(): Composer 5 | fun unindent(): Composer 6 | fun newElement(): Composer 7 | fun newAttribute(): Composer 8 | fun append(value: String): Composer 9 | fun appendLine(): Composer 10 | } 11 | 12 | internal class XmlComposer(private val sb: StringBuilder) : Composer { 13 | override fun indent(): Composer = this 14 | override fun unindent(): Composer = this 15 | override fun newElement(): Composer = this 16 | override fun newAttribute(): Composer = also { sb.append(" ") } 17 | override fun append(value: String): Composer = also { sb.append(value) } 18 | override fun appendLine() = this 19 | } 20 | 21 | internal class PrettyPrintXmlComposer( 22 | private val sb: StringBuilder, 23 | private val indent: Int = 2, 24 | ) : Composer { 25 | 26 | private var level = 0 27 | 28 | override fun indent(): Composer = also { 29 | level++ 30 | } 31 | 32 | override fun unindent(): Composer = also { 33 | level-- 34 | } 35 | 36 | override fun newElement(): Composer = also { 37 | appendLine() 38 | } 39 | 40 | override fun newAttribute(): Composer = also { 41 | appendLine().append(" ".repeat(indent)) 42 | } 43 | 44 | override fun append(value: String): Composer = also { sb.append(value) } 45 | 46 | override fun appendLine(): Composer = also { 47 | sb.appendLine() 48 | sb.append(" ".repeat(level * indent)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/internal/StreamingXmlDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml.internal 2 | 3 | import com.ryanharter.kotlinx.serialization.xml.UndefinedNamespaceException 4 | import com.ryanharter.kotlinx.serialization.xml.Xml 5 | import com.ryanharter.kotlinx.serialization.xml.XmlContent 6 | import com.ryanharter.kotlinx.serialization.xml.XmlDecoder 7 | import com.ryanharter.kotlinx.serialization.xml.XmlEntity 8 | import com.ryanharter.kotlinx.serialization.xml.XmlName 9 | import com.ryanharter.kotlinx.serialization.xml.XmlNamespace 10 | import kotlinx.serialization.DeserializationStrategy 11 | import kotlinx.serialization.ExperimentalSerializationApi 12 | import kotlinx.serialization.descriptors.SerialDescriptor 13 | import kotlinx.serialization.encoding.CompositeDecoder 14 | import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE 15 | import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME 16 | import kotlinx.serialization.encoding.Decoder 17 | import kotlinx.serialization.modules.SerializersModule 18 | 19 | private const val DEFAULT_NAMESPACE = "" 20 | 21 | @OptIn(ExperimentalSerializationApi::class) 22 | internal class XmlElementDecoder( 23 | private val decoder: StreamingXmlDecoder, 24 | private val lexer: XmlLexer, 25 | descriptor: SerialDescriptor, 26 | parentNamespaceMap: Map = emptyMap(), 27 | ) : CompositeDecoder { 28 | private var lastTextToken: XmlLexer.Token.Text? = null 29 | 30 | private val namespaceMap = parentNamespaceMap.toMutableMap() 31 | 32 | private data class Name(val name: String, val namespaceUri: String?) 33 | private val elementNames = (0 until descriptor.elementsCount).map { i -> 34 | val name = descriptor.getElementAnnotations(i) 35 | .filterIsInstance() 36 | .firstOrNull() 37 | ?.name ?: descriptor.getElementName(i) 38 | val namespace = (descriptor.getElementAnnotations(i) + descriptor.getElementDescriptor(i).annotations) 39 | .filterIsInstance() 40 | .firstOrNull() 41 | ?.uri 42 | Name(name, namespace) 43 | } 44 | 45 | override val serializersModule: SerializersModule = decoder.xml.serializersModule 46 | 47 | init { 48 | // Skip whitespace at the beginning of the file, if any. 49 | lexer.skipWhitespace() 50 | // Consume the start token if it exists. 51 | if (lexer.peek() == '<') { 52 | val startElement = lexer.readNextToken() 53 | require(startElement is XmlLexer.Token.ElementStart) 54 | collectNamespaces() 55 | } 56 | } 57 | 58 | private fun getElementIndex(name: String, namespace: String?): Int { 59 | val namespaceUri = namespace?.let { 60 | namespaceMap[it] ?: throw UndefinedNamespaceException(it) 61 | } 62 | val index = elementNames.indexOfFirst { 63 | it.name == name && it.namespaceUri == namespaceUri 64 | } 65 | return if (index > -1) index else UNKNOWN_NAME 66 | } 67 | 68 | // Copies the lexer to read ahead and collect all namespaces defined in the start element tag. 69 | private fun collectNamespaces() { 70 | val l = lexer.copy() 71 | var t = l.readNextToken() 72 | while (t !is XmlLexer.Token.ElementStartEnd && t !is XmlLexer.Token.ElementEnd) { 73 | if (t is XmlLexer.Token.AttributeName && (t.namespace == "xmlns" || t.name == "xmlns")) { 74 | val localName = if (t.namespace == "xmlns") t.name else DEFAULT_NAMESPACE 75 | 76 | val namespaceUri = l.readNextToken() 77 | require(namespaceUri is XmlLexer.Token.AttributeValue) 78 | namespaceMap[localName] = namespaceUri.value 79 | } 80 | 81 | t = l.readNextToken() 82 | } 83 | } 84 | 85 | override fun decodeElementIndex(descriptor: SerialDescriptor): Int { 86 | while (true) { 87 | when (val token = lexer.readNextToken()) { 88 | is XmlLexer.Token.ElementStartEnd -> continue 89 | is XmlLexer.Token.ElementEnd -> return DECODE_DONE 90 | is XmlLexer.Token.ElementStart -> { 91 | collectNamespaces() 92 | return getElementIndex(token.name, token.namespace) 93 | } 94 | is XmlLexer.Token.AttributeName -> { 95 | // Namespaces have already been read when we consumed the ElementStart token. 96 | if (token.namespace == "xmlns" || token.name == "xmlns") { 97 | require(lexer.readNextToken() is XmlLexer.Token.AttributeValue) 98 | continue 99 | } 100 | 101 | return getElementIndex(token.name, token.namespace) 102 | } 103 | is XmlLexer.Token.Text -> { 104 | lastTextToken = token 105 | 106 | val index = (0 until descriptor.elementsCount).indexOfFirst { i -> 107 | descriptor.getElementAnnotations(i).any { it is XmlContent } 108 | } 109 | return if (index == -1) UNKNOWN_NAME else index 110 | } 111 | else -> return UNKNOWN_NAME 112 | } 113 | } 114 | } 115 | 116 | override fun endStructure(descriptor: SerialDescriptor) { 117 | // no op 118 | } 119 | 120 | override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean { 121 | return when (val t = lexer.readNextToken()) { 122 | is XmlLexer.Token.AttributeValue -> t.value.toBoolean() 123 | is XmlLexer.Token.Text -> t.content.toBoolean() 124 | // If the element or attribute ends immediately, it's presence makes it true 125 | is XmlLexer.Token.ElementEnd, is XmlLexer.Token.AttributeEnd -> true 126 | else -> throw IllegalArgumentException("Invalid boolean value") 127 | } 128 | } 129 | 130 | override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte { 131 | return decodeStringElement(descriptor, index).toByte() 132 | } 133 | 134 | override fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char { 135 | return decodeStringElement(descriptor, index).toCharArray()[0] 136 | } 137 | 138 | override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { 139 | return super.decodeCollectionSize(descriptor) 140 | } 141 | 142 | override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double { 143 | return decodeStringElement(descriptor, index).toDouble() 144 | } 145 | 146 | override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float { 147 | return decodeStringElement(descriptor, index).toFloat() 148 | } 149 | 150 | @ExperimentalSerializationApi 151 | override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder { 152 | TODO("Not yet implemented") 153 | } 154 | 155 | override fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int { 156 | return decodeStringElement(descriptor, index).toInt() 157 | } 158 | 159 | override fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long { 160 | return decodeStringElement(descriptor, index).toLong() 161 | } 162 | 163 | @ExperimentalSerializationApi 164 | override fun decodeNullableSerializableElement( 165 | descriptor: SerialDescriptor, 166 | index: Int, 167 | deserializer: DeserializationStrategy, 168 | previousValue: T? 169 | ): T? { 170 | TODO("Not yet implemented") 171 | } 172 | 173 | @ExperimentalSerializationApi 174 | override fun decodeSequentially(): Boolean { 175 | return super.decodeSequentially() 176 | } 177 | 178 | override fun decodeSerializableElement( 179 | descriptor: SerialDescriptor, 180 | index: Int, 181 | deserializer: DeserializationStrategy, 182 | previousValue: T? 183 | ): T { 184 | return deserializer.deserialize(decoder.copy(namespaceMap)) 185 | } 186 | 187 | override fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short { 188 | return decodeStringElement(descriptor, index).toShort() 189 | } 190 | 191 | override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String { 192 | val lastContent = lastTextToken?.content 193 | if (lastContent != null) { 194 | lastTextToken = null 195 | return lastContent 196 | } 197 | 198 | return when (val t = lexer.readNextToken()) { 199 | is XmlLexer.Token.AttributeValue -> t.value 200 | else -> throw IllegalArgumentException("Invalid string value: $t") 201 | } 202 | } 203 | } 204 | 205 | internal class StreamingXmlDecoder( 206 | override val xml: Xml, 207 | private val lexer: XmlLexer, 208 | private val namespaceMap: Map = emptyMap(), 209 | ) : XmlDecoder { 210 | override val serializersModule: SerializersModule = xml.serializersModule 211 | 212 | internal fun copy( 213 | namespaceMap: Map = this.namespaceMap, 214 | ): StreamingXmlDecoder = StreamingXmlDecoder(xml, lexer, namespaceMap) 215 | 216 | override fun decodeXmlEntity(): XmlEntity { 217 | TODO("Not yet implemented") 218 | } 219 | 220 | override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { 221 | return XmlElementDecoder(this, lexer, descriptor, namespaceMap) 222 | } 223 | 224 | override fun decodeBoolean(): Boolean { 225 | TODO("Not yet implemented") 226 | } 227 | 228 | override fun decodeByte(): Byte { 229 | TODO("Not yet implemented") 230 | } 231 | 232 | override fun decodeChar(): Char { 233 | TODO("Not yet implemented") 234 | } 235 | 236 | override fun decodeDouble(): Double { 237 | TODO("Not yet implemented") 238 | } 239 | 240 | override fun decodeEnum(enumDescriptor: SerialDescriptor): Int { 241 | TODO("Not yet implemented") 242 | } 243 | 244 | override fun decodeFloat(): Float { 245 | TODO("Not yet implemented") 246 | } 247 | 248 | @ExperimentalSerializationApi 249 | override fun decodeInline(descriptor: SerialDescriptor): Decoder { 250 | TODO("Not yet implemented") 251 | } 252 | 253 | override fun decodeInt(): Int { 254 | TODO("Not yet implemented") 255 | } 256 | 257 | override fun decodeLong(): Long { 258 | TODO("Not yet implemented") 259 | } 260 | 261 | @ExperimentalSerializationApi 262 | override fun decodeNotNullMark(): Boolean { 263 | TODO("Not yet implemented") 264 | } 265 | 266 | @ExperimentalSerializationApi 267 | override fun decodeNull(): Nothing? { 268 | TODO("Not yet implemented") 269 | } 270 | 271 | override fun decodeShort(): Short { 272 | TODO("Not yet implemented") 273 | } 274 | 275 | override fun decodeString(): String { 276 | TODO("Not yet implemented") 277 | } 278 | 279 | } 280 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/internal/StreamingXmlEncoder.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml.internal 2 | 3 | import com.ryanharter.kotlinx.serialization.xml.Xml 4 | import com.ryanharter.kotlinx.serialization.xml.XmlAttribute 5 | import com.ryanharter.kotlinx.serialization.xml.XmlContent 6 | import com.ryanharter.kotlinx.serialization.xml.XmlDefaultNamespace 7 | import com.ryanharter.kotlinx.serialization.xml.XmlEncoder 8 | import com.ryanharter.kotlinx.serialization.xml.XmlEntity 9 | import com.ryanharter.kotlinx.serialization.xml.XmlNamespace 10 | import kotlinx.serialization.ExperimentalSerializationApi 11 | import kotlinx.serialization.SerializationException 12 | import kotlinx.serialization.SerializationStrategy 13 | import kotlinx.serialization.descriptors.SerialDescriptor 14 | import kotlinx.serialization.encoding.CompositeEncoder 15 | import kotlinx.serialization.encoding.Encoder 16 | import kotlinx.serialization.modules.SerializersModule 17 | 18 | @OptIn(ExperimentalSerializationApi::class) 19 | internal class StreamingXmlEncoder( 20 | override val xml: Xml, 21 | private val composer: Composer, 22 | private val namespaces: Map = emptyMap(), 23 | ) : XmlEncoder { 24 | override val serializersModule: SerializersModule = xml.serializersModule 25 | 26 | private var startTagClosed = false 27 | 28 | override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { 29 | composer.indent().newElement() 30 | composer.append("<") 31 | 32 | // Get the namespace of the element 33 | val namespace = descriptor.annotations 34 | .filterIsInstance() 35 | .firstOrNull { it.localName.isNotBlank() } 36 | ?.let { namespaces[it.uri] ?: it } 37 | 38 | if (namespace != null && namespace.localName.isNotBlank()) { 39 | composer.append(namespace.localName).append(":") 40 | } 41 | composer.append(descriptor.serialName) 42 | 43 | // We only define the namespace if it isn't already in scope 44 | if (namespace != null && !namespaces.contains(namespace.uri)) { 45 | composer.newAttribute().append(namespace) 46 | } 47 | 48 | // Check for a default namespace 49 | val defaultNamespace = descriptor.annotations.filterIsInstance().firstOrNull() 50 | if (defaultNamespace != null) { 51 | composer.newAttribute().append(defaultNamespace) 52 | } 53 | 54 | // Collect all new child namespaces 55 | val newNamespaces = (0 until descriptor.elementsCount) 56 | // XmlContent annotated properties are encoded as text, not elements or annotations, so 57 | // we don't need their namespaces. 58 | .filterNot { 59 | descriptor.getElementAnnotations(it).filterIsInstance().isNotEmpty() 60 | } 61 | .flatMap { 62 | descriptor.getElementAnnotations(it).filterIsInstance() + 63 | descriptor.getElementDescriptor(it).annotations.filterIsInstance() 64 | } 65 | .filterNot { namespaces.contains(it.uri) } 66 | .associateBy { it.uri } 67 | 68 | newNamespaces.values.forEach { ns -> 69 | composer.newAttribute().append(ns) 70 | } 71 | 72 | val childNamespaces = namespaces.toMutableMap() 73 | childNamespaces += newNamespaces 74 | namespace?.let { childNamespaces[it.uri] = it } 75 | 76 | return StreamingXmlEncoder( 77 | xml, composer, childNamespaces 78 | ) 79 | } 80 | 81 | private fun Composer.append(namespace: XmlNamespace): Composer { 82 | append("xmlns") 83 | if (namespace.localName.isNotBlank()) { 84 | append(":").append(namespace.localName) 85 | } 86 | append("=") 87 | encodeString(namespace.uri) 88 | return this 89 | } 90 | 91 | private fun Composer.append(namespace: XmlDefaultNamespace): Composer { 92 | append("xmlns").append("=") 93 | encodeString(namespace.uri) 94 | return this 95 | } 96 | 97 | override fun endStructure(descriptor: SerialDescriptor) { 98 | if (!startTagClosed) { 99 | composer.append("/>").unindent() 100 | return 101 | } 102 | 103 | composer.unindent().appendLine().append("() 106 | .firstOrNull() 107 | ?.let { 108 | namespaces[it.uri] ?: it 109 | } // If the NS is already defined we don't need to redefine it. 110 | if (namespace != null && namespace.localName.isNotBlank()) { 111 | composer.append(namespace.localName).append(":") 112 | } 113 | composer.append(descriptor.serialName).append(">") 114 | } 115 | 116 | private fun SerialDescriptor.getNamespace(index: Int): XmlNamespace? { 117 | return (getElementAnnotations(index).filterIsInstance().firstOrNull() 118 | ?: getElementDescriptor(index).annotations.filterIsInstance().firstOrNull()) 119 | ?.let { namespaces[it.uri] ?: it } 120 | } 121 | 122 | override fun encodeNull() { 123 | throw SerializationException("'null' is not supported by default") 124 | } 125 | 126 | private fun encodeValue(value: Any) { 127 | val quoteChar = if (startTagClosed) "" else "\"" 128 | composer.append(quoteChar).append(value.toString()).append(quoteChar) 129 | } 130 | 131 | private fun encodeElement(descriptor: SerialDescriptor, index: Int, value: Any) { 132 | if (descriptor.getElementAnnotations(index).filterIsInstance().isNotEmpty()) { 133 | require(!startTagClosed) { 134 | "Property ${descriptor.getElementName(index)} annotated with XmlAttribute after non-annotated properties." 135 | } 136 | 137 | encodeAttribute(descriptor, index, value) 138 | return 139 | } 140 | 141 | if (!startTagClosed) { 142 | startTagClosed = true 143 | composer.append(">").indent().appendLine() 144 | } 145 | 146 | // If this is a content element then we only need to write the value 147 | if (descriptor.getElementAnnotations(index).filterIsInstance().isNotEmpty()) { 148 | composer.append(value.toString()) 149 | return 150 | } 151 | 152 | val prefix = descriptor.getNamespace(index)?.localName?.let { "$it:" } ?: "" 153 | val elementName = descriptor.getElementName(index) 154 | val tagName = "$prefix$elementName" 155 | 156 | composer.append("<").append(tagName).append(">") 157 | composer.append(value.toString()) 158 | composer.append("") 159 | } 160 | 161 | override fun encodeSerializableElement( 162 | descriptor: SerialDescriptor, 163 | index: Int, 164 | serializer: SerializationStrategy, 165 | value: T 166 | ) { 167 | if (!startTagClosed) { 168 | startTagClosed = true 169 | composer.append(">") 170 | } 171 | encodeSerializableValue(serializer, value) 172 | } 173 | 174 | private fun encodeAttribute(descriptor: SerialDescriptor, index: Int, value: Any) { 175 | composer.newAttribute() 176 | descriptor.getNamespace(index)?.localName?.let { localName -> 177 | composer.append(localName).append(":") 178 | } 179 | composer.append(descriptor.getElementName(index)) 180 | 181 | // TODO only add this if Xml.config.encodeBooleans == true 182 | composer.append("=") 183 | encodeValue(value.toString()) 184 | } 185 | 186 | override fun encodeBoolean(value: Boolean): Unit = encodeValue(value) 187 | override fun encodeByte(value: Byte): Unit = encodeValue(value) 188 | override fun encodeShort(value: Short): Unit = encodeValue(value) 189 | override fun encodeInt(value: Int): Unit = encodeValue(value) 190 | override fun encodeLong(value: Long): Unit = encodeValue(value) 191 | override fun encodeFloat(value: Float): Unit = encodeValue(value) 192 | override fun encodeDouble(value: Double): Unit = encodeValue(value) 193 | override fun encodeChar(value: Char): Unit = encodeValue(value) 194 | override fun encodeString(value: String): Unit = encodeValue(value) 195 | override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int): Unit = encodeValue(index) 196 | override fun encodeInline(descriptor: SerialDescriptor): Encoder = this 197 | 198 | // Delegating implementation of CompositeEncoder 199 | override fun encodeBooleanElement( 200 | descriptor: SerialDescriptor, 201 | index: Int, 202 | value: Boolean 203 | ): Unit = encodeElement(descriptor, index, value) 204 | 205 | override fun encodeByteElement(descriptor: SerialDescriptor, index: Int, value: Byte): Unit = 206 | encodeElement(descriptor, index, value) 207 | 208 | override fun encodeShortElement(descriptor: SerialDescriptor, index: Int, value: Short): Unit = 209 | encodeElement(descriptor, index, value) 210 | 211 | override fun encodeIntElement(descriptor: SerialDescriptor, index: Int, value: Int): Unit = 212 | encodeElement(descriptor, index, value) 213 | 214 | override fun encodeLongElement(descriptor: SerialDescriptor, index: Int, value: Long): Unit = 215 | encodeElement(descriptor, index, value) 216 | 217 | override fun encodeFloatElement(descriptor: SerialDescriptor, index: Int, value: Float): Unit = 218 | encodeElement(descriptor, index, value) 219 | 220 | override fun encodeDoubleElement(descriptor: SerialDescriptor, index: Int, value: Double): Unit = 221 | encodeElement(descriptor, index, value) 222 | 223 | override fun encodeCharElement(descriptor: SerialDescriptor, index: Int, value: Char): Unit = 224 | encodeElement(descriptor, index, value) 225 | 226 | override fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String): Unit = 227 | encodeElement(descriptor, index, value) 228 | 229 | @ExperimentalSerializationApi 230 | override fun encodeNullableSerializableElement( 231 | descriptor: SerialDescriptor, 232 | index: Int, 233 | serializer: SerializationStrategy, 234 | value: T? 235 | ) { 236 | TODO("Not yet implemented") 237 | } 238 | 239 | @ExperimentalSerializationApi 240 | override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder { 241 | TODO("Not yet implemented") 242 | } 243 | 244 | override fun encodeXmlEntity(entity: XmlEntity) { 245 | encodeSerializableValue(XmlEntitySerializer, entity) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/internal/XmlEntitySerializer.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalSerializationApi::class) 2 | 3 | package com.ryanharter.kotlinx.serialization.xml.internal 4 | 5 | import com.ryanharter.kotlinx.serialization.xml.XmlDecoder 6 | import com.ryanharter.kotlinx.serialization.xml.XmlEncoder 7 | import com.ryanharter.kotlinx.serialization.xml.XmlEntity 8 | import com.ryanharter.kotlinx.serialization.xml.XmlEntity.Attribute 9 | import com.ryanharter.kotlinx.serialization.xml.XmlEntity.Value 10 | import kotlinx.serialization.ExperimentalSerializationApi 11 | import kotlinx.serialization.KSerializer 12 | import kotlinx.serialization.Serializer 13 | import kotlinx.serialization.builtins.PairSerializer 14 | import kotlinx.serialization.builtins.serializer 15 | import kotlinx.serialization.descriptors.PrimitiveKind 16 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 17 | import kotlinx.serialization.descriptors.SerialDescriptor 18 | import kotlinx.serialization.descriptors.buildClassSerialDescriptor 19 | import kotlinx.serialization.encoding.Decoder 20 | import kotlinx.serialization.encoding.Encoder 21 | 22 | @Serializer(forClass = XmlEntity::class) 23 | @PublishedApi 24 | internal object XmlEntitySerializer : KSerializer { 25 | override val descriptor: SerialDescriptor = 26 | buildClassSerialDescriptor("com.ryanharter.kotlinx.serialization.xml.XmlEntity") { 27 | element("XmlValue", XmlValueSerializer.descriptor) 28 | element("XmlAttribute", XmlAttributeSerializer.descriptor) 29 | } 30 | 31 | override fun serialize(encoder: Encoder, value: XmlEntity) { 32 | verify(encoder) 33 | when (value) { 34 | is Attribute -> encoder.encodeSerializableValue(XmlAttributeSerializer, value) 35 | is Value -> encoder.encodeSerializableValue(XmlValueSerializer, value) 36 | else -> {} // no op 37 | } 38 | } 39 | 40 | override fun deserialize(decoder: Decoder): XmlEntity { 41 | val input = decoder.asXmlDecoder() 42 | return input.decodeXmlEntity() 43 | } 44 | 45 | } 46 | 47 | @Serializer(forClass = Attribute::class) 48 | @PublishedApi 49 | internal object XmlAttributeSerializer : KSerializer { 50 | override val descriptor: SerialDescriptor = 51 | PrimitiveSerialDescriptor( 52 | "com.ryanharter.kotlinx.serialization.xml.XmlEntity.XmlAttribute", 53 | PrimitiveKind.STRING 54 | ) 55 | 56 | override fun serialize(encoder: Encoder, value: Attribute) { 57 | verify(encoder) 58 | PairSerializer(String.serializer(), String.serializer()).serialize( 59 | encoder, 60 | value.name to value.value 61 | ) 62 | } 63 | 64 | override fun deserialize(decoder: Decoder): Attribute { 65 | val result = decoder.asXmlDecoder().decodeXmlEntity() 66 | if (result !is Attribute) throw IllegalArgumentException("Unexpected XML entity, expected XmlAttribute, got ${result::class}") 67 | return result 68 | } 69 | 70 | } 71 | 72 | private object XmlValueSerializer : KSerializer { 73 | override val descriptor: SerialDescriptor = 74 | PrimitiveSerialDescriptor( 75 | "com.ryanharter.kotlinx.serialization.xml.XmlValue", 76 | PrimitiveKind.STRING 77 | ) 78 | 79 | override fun serialize(encoder: Encoder, value: Value) { 80 | verify(encoder) 81 | encoder.encodeString(value.value) 82 | } 83 | 84 | override fun deserialize(decoder: Decoder): Value { 85 | val result = decoder.asXmlDecoder().decodeXmlEntity() 86 | if (result !is Value) throw IllegalArgumentException("Unexpected XML entity, expected XmlValue, got ${result::class}") 87 | return result 88 | } 89 | 90 | } 91 | 92 | private fun verify(decoder: Decoder) { 93 | decoder.asXmlDecoder() 94 | } 95 | 96 | private fun verify(encoder: Encoder) { 97 | encoder.asXmlEncoder() 98 | } 99 | 100 | internal fun Decoder.asXmlDecoder() = this as? XmlDecoder 101 | ?: throw IllegalStateException( 102 | "This serializer can be used only with Xml format. " + 103 | "Expected Decoder to be XmlDecoder, got ${this::class}" 104 | ) 105 | 106 | internal fun Encoder.asXmlEncoder() = this as? XmlEncoder 107 | ?: throw IllegalStateException( 108 | "This serializer can be used only with Xml format. " + 109 | "Expected Encoder to be XmlEncoder, got ${this::class}" 110 | ) 111 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/com/ryanharter/kotlinx/serialization/xml/internal/XmlLexer.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml.internal 2 | 3 | internal class XmlLexer(private val source: String) { 4 | var position = 0 5 | 6 | private var lastToken: Token = Token.None 7 | 8 | fun copy(): XmlLexer { 9 | val other = XmlLexer(source) 10 | other.position = position 11 | other.lastToken = lastToken 12 | return other 13 | } 14 | 15 | fun next(): Char? = if (position < source.length) source[position++] else null 16 | fun peek(): Char? = if (position < source.length) source[position] else null 17 | fun skipToChar(char: Char) { 18 | var c = next() 19 | while (c != null) { 20 | when (c) { 21 | char -> return 22 | else -> c = next() 23 | } 24 | } 25 | } 26 | 27 | fun skipWhitespace() { 28 | var c = peek() 29 | while (c != null) { 30 | when (c) { 31 | ' ', '\n', '\t', '\r' -> { 32 | next() 33 | c = peek() 34 | } 35 | else -> return 36 | } 37 | } 38 | } 39 | 40 | fun requireChar(char: Char) { 41 | skipWhitespace() 42 | require(peek() != null) { "Unexpected end of file" } 43 | require(peek() == char) { "Unexpected token ${peek()}, expecting $char" } 44 | } 45 | 46 | data class QualifiedName(val name: String, val namespace: String? = null) 47 | 48 | private fun readElementName(): QualifiedName { 49 | skipWhitespace() 50 | var start = position 51 | var namespace: String? = null 52 | while (true) { 53 | when (next()) { 54 | null -> throw IllegalArgumentException("Unexpected end of file") 55 | ':' -> { 56 | namespace = source.substring(start, position - 1) 57 | start = position 58 | } 59 | '\r', '\n', '\t', ' ', '>', '/' -> break 60 | } 61 | } 62 | val name = source.substring(start, --position) 63 | return QualifiedName(name, namespace) 64 | } 65 | 66 | private fun readAttributeName(): QualifiedName { 67 | skipWhitespace() 68 | var start = position 69 | var namespace: String? = null 70 | while (true) { 71 | when (peek()) { 72 | null -> throw IllegalArgumentException("Unexpected end of file") 73 | ':' -> { 74 | namespace = source.substring(start, position++) 75 | start = position 76 | } 77 | '\r', '\t', '\n', ' ', '=' -> break 78 | else -> position++ 79 | } 80 | } 81 | val name = source.substring(start, position) 82 | return QualifiedName(name, namespace) 83 | } 84 | 85 | private fun readAttributeValue(): String { 86 | skipWhitespace() 87 | var quote = next() 88 | while (quote != null && quote != '\'' && quote != '"') { 89 | quote = next() 90 | } 91 | requireNotNull(quote) { "Unexpected end of file" } 92 | 93 | val s = position 94 | var c = next() 95 | while (true) { 96 | when (c) { 97 | null -> throw IllegalArgumentException("Unexpected end of file") 98 | '<', '&' -> throw IllegalArgumentException("Invalid character in attribute name: $c") 99 | quote -> break 100 | else -> c = next() 101 | } 102 | } 103 | return source.substring(s, position - 1) 104 | } 105 | 106 | fun readText(): String { 107 | skipWhitespace() 108 | val text = StringBuilder() 109 | while (true) { 110 | when (val c = next()) { 111 | null -> throw IllegalArgumentException("Unexpected end of file") 112 | '<' -> { 113 | if (peek() == '!' && source.substring(position, position + 8) == "![CDATA[") { 114 | position += 8 115 | val end = source.indexOf("]]>", position) 116 | text.append(source.substring(position, end)) 117 | position = end + 3 118 | } else { 119 | position-- 120 | break 121 | } 122 | } 123 | else -> text.append(c) 124 | } 125 | } 126 | return text.toString().trim() 127 | } 128 | 129 | fun readNextToken(): Token { 130 | when (lastToken) { 131 | is Token.DocumentEnd -> return lastToken 132 | is Token.None, Token.ElementStartEnd, is Token.ElementEnd, is Token.Text -> { 133 | skipWhitespace() 134 | while (true) { 135 | skipWhitespace() 136 | when (peek()) { 137 | null -> return Token.DocumentEnd.also { lastToken = it } 138 | '<' -> { 139 | next() // consume the bracket 140 | when (peek()) { 141 | '!', '?' -> { 142 | skipToChar('>') 143 | } 144 | '/' -> { 145 | next() // consume the slash 146 | val elementName = readElementName() 147 | skipWhitespace() 148 | next() // Consume the closing bracket 149 | return Token.ElementEnd(elementName.name, elementName.namespace) 150 | .also { lastToken = it } 151 | } 152 | else -> { 153 | val elementName = readElementName() 154 | return Token.ElementStart(elementName.name, elementName.namespace) 155 | .also { lastToken = it } 156 | } 157 | } 158 | } 159 | '/' -> { 160 | skipToChar('>') 161 | return Token.ElementEnd() 162 | } 163 | else -> { 164 | return Token.Text(readText()).also { lastToken = it } 165 | } 166 | } 167 | } 168 | } 169 | is Token.ElementStart, is Token.AttributeValue, is Token.AttributeEnd -> { 170 | while (true) { 171 | skipWhitespace() 172 | return when (peek()) { 173 | '/' -> { 174 | skipToChar('>') 175 | Token.ElementEnd() 176 | } 177 | '>' -> { 178 | next() // consume the bracket 179 | Token.ElementStartEnd 180 | } 181 | else -> { 182 | val qname = readAttributeName() 183 | Token.AttributeName(qname.name, qname.namespace) 184 | } 185 | }.also { lastToken = it } 186 | } 187 | } 188 | is Token.AttributeName -> { 189 | skipWhitespace() 190 | return if (peek() == '=') { 191 | position++ 192 | Token.AttributeValue(readAttributeValue()).also { lastToken = it } 193 | } else { 194 | Token.AttributeEnd.also { lastToken = it } 195 | } 196 | } 197 | } 198 | } 199 | 200 | sealed interface Token { 201 | object None : Token 202 | data class ElementStart(val name: String, val namespace: String? = null) : Token 203 | object ElementStartEnd : Token 204 | data class ElementEnd(val name: String? = null, val namespace: String? = null) : Token 205 | data class AttributeName(val name: String, val namespace: String? = null) : Token 206 | data class AttributeValue(val value: String) : Token 207 | object AttributeEnd : Token 208 | data class Text(val content: String) : Token 209 | object DocumentEnd : Token 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/com/ryanharter/kotlinx/serialization/xml/XmlDecoderTest.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.decodeFromString 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | import kotlin.test.fail 9 | 10 | class XmlDecoderTest { 11 | 12 | private val default = Xml.Default 13 | 14 | @Test 15 | fun basicXml() { 16 | val xml = """ 17 | 18 | Hi 19 | 20 | """.trimIndent() 21 | val actual = default.decodeFromString(xml) 22 | assertEquals( 23 | Greeting( 24 | from = "Ryan", 25 | to = "Bill", 26 | message = Message("Hi") 27 | ), actual 28 | ) 29 | } 30 | 31 | @Test 32 | fun xmlWithNestedAttribute() { 33 | val xml = """ 34 | 35 | 36 | 37 | """.trimIndent() 38 | val actual = default.decodeFromString(xml) 39 | assertEquals( 40 | Greeting( 41 | from = "Ryan", 42 | to = "Bill", 43 | message = Message("Hi") 44 | ), actual 45 | ) 46 | } 47 | 48 | @Serializable 49 | data class NamespacedGreetings( 50 | @SerialName("greeting") 51 | val myGreeting: MyGreeting, 52 | @XmlName("greeting") 53 | @XmlNamespace("http://greetings.example.com/schema", "") 54 | val otherGreeting: OtherGreeting, 55 | ) 56 | 57 | @Serializable 58 | data class MyGreeting(@XmlContent val message: String) 59 | 60 | @Serializable 61 | data class OtherGreeting(@XmlContent val message: String) 62 | 63 | @Test 64 | fun withElementNamespaces() { 65 | val xml = """ 66 | 67 | No namespaces here! 68 | Who is this? 69 | 70 | """.trimIndent() 71 | val actual = default.decodeFromString(xml) 72 | assertEquals( 73 | NamespacedGreetings( 74 | MyGreeting("No namespaces here!"), 75 | OtherGreeting("Who is this?"), 76 | ), actual 77 | ) 78 | } 79 | 80 | @Test 81 | fun undefinedNamespaces() { 82 | val xml = """ 83 | 84 | No namespaces here! 85 | Who is this? 86 | 87 | """.trimIndent() 88 | try { 89 | default.decodeFromString(xml) 90 | fail("Expected UndefinedNamespaceException.") 91 | } catch (e: UndefinedNamespaceException) { 92 | // success 93 | } 94 | } 95 | 96 | @Serializable 97 | data class Attributes( 98 | val unannotated: String, 99 | @XmlAttribute 100 | val unnamed: String, 101 | @XmlAttribute 102 | @XmlName("namedAttribute") 103 | val named: String, 104 | @XmlAttribute 105 | @XmlName("namedNamespaced") 106 | @XmlNamespace("http://greetings.example.com/schema", "") 107 | val namedAndNamespaced: String, 108 | @XmlAttribute 109 | @XmlNamespace("http://greetings.example.com/schema", "") 110 | val namespaced: String, 111 | @XmlNamespace("http://greetings.example.com/schema", "") 112 | val onlyNamespaced: String, 113 | ) 114 | 115 | @Test 116 | fun attributeNamespaces() { 117 | val xml = """ 118 | 125 | """.trimIndent() 126 | val actual = default.decodeFromString(xml) 127 | assertEquals( 128 | Attributes( 129 | "first", 130 | "second", 131 | "third", 132 | "fourth", 133 | "fifth", 134 | "sixth", 135 | ), actual 136 | ) 137 | } 138 | 139 | @Test 140 | fun namespacedAttributeBeforeNamespaceDecl() { 141 | val xml = """ 142 | 150 | """.trimIndent() 151 | val actual = default.decodeFromString(xml) 152 | assertEquals( 153 | Attributes( 154 | "first", 155 | "second", 156 | "third", 157 | "fourth", 158 | "fifth", 159 | "sixth", 160 | ), actual 161 | ) 162 | } 163 | 164 | @Serializable 165 | @XmlNamespace("http://etherx.jabber.org/streams", "") 166 | data class Stream(val from: String, val to: String) 167 | 168 | @Test 169 | fun namespacedElementContainingNamespaceDecl() { 170 | val xml = """ 171 | 175 | """.trimIndent() 176 | val actual = default.decodeFromString(xml) 177 | assertEquals(Stream(from = "source@xmpp.org", to = "dest@xmpp.org"), actual) 178 | } 179 | 180 | @Serializable 181 | data class StreamHolder(val stream: Stream) 182 | 183 | @Test 184 | fun embeddedNamespacedElementContainingNamespaceDecl() { 185 | val xml = """ 186 | 187 | 191 | 192 | """.trimIndent() 193 | val actual = default.decodeFromString(xml) 194 | assertEquals(StreamHolder(Stream(from = "source@xmpp.org", to = "dest@xmpp.org")), actual) 195 | } 196 | 197 | @Test 198 | fun skipsComments() { 199 | val xml = """ 200 | 201 | 202 | 203 | Hi 204 | 205 | """.trimIndent() 206 | val actual = default.decodeFromString(xml) 207 | assertEquals( 208 | Greeting( 209 | from = "Ryan", 210 | to = "Bill", 211 | message = Message("Hi") 212 | ), actual 213 | ) 214 | } 215 | 216 | @Test 217 | fun skipsXmlDecl() { 218 | val xml = """ 219 | 220 | 221 | Hi 222 | 223 | """.trimIndent() 224 | val actual = default.decodeFromString(xml) 225 | assertEquals( 226 | Greeting( 227 | from = "Ryan", 228 | to = "Bill", 229 | message = Message("Hi") 230 | ), actual 231 | ) 232 | } 233 | 234 | @Test 235 | fun skipsWhitespaceBeforeXml() { 236 | val xml = """ 237 | 238 | 239 | Hi 240 | 241 | """.trimIndent() 242 | val actual = default.decodeFromString(xml) 243 | assertEquals( 244 | Greeting( 245 | from = "Ryan", 246 | to = "Bill", 247 | message = Message("Hi") 248 | ), actual 249 | ) 250 | } 251 | 252 | @Test 253 | fun readsBooleanPresenceAsTrue() { 254 | @Serializable 255 | data class Data( 256 | val required: Boolean = true, 257 | ) 258 | 259 | val xml = """ 260 | 261 | """.trimIndent() 262 | val actual = default.decodeFromString(xml) 263 | assertEquals(Data(true), actual) 264 | } 265 | 266 | @Test 267 | fun readsBooleanAttributePresenceAsTrue() { 268 | @Serializable 269 | data class Data( 270 | @XmlAttribute val required: Boolean = false, 271 | @XmlAttribute val snakeCount: Int = 4, 272 | ) 273 | 274 | val xml = """ 275 | 276 | """.trimIndent() 277 | val actual = default.decodeFromString(xml) 278 | assertEquals(Data(true), actual) 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/com/ryanharter/kotlinx/serialization/xml/XmlEncoderTest.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalSerializationApi::class) 2 | 3 | package com.ryanharter.kotlinx.serialization.xml 4 | 5 | import kotlinx.serialization.ExperimentalSerializationApi 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | import kotlinx.serialization.encodeToString 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | 12 | @Serializable 13 | @SerialName("Greeting") 14 | data class Greeting( 15 | @XmlAttribute val from: String, 16 | @XmlAttribute val to: String, 17 | val message: Message 18 | ) 19 | 20 | @Serializable 21 | @SerialName("Message") 22 | data class Message( 23 | @XmlContent val content: String 24 | ) 25 | 26 | class XmlEncoderTest { 27 | 28 | private val default = Xml.Default 29 | 30 | @Test 31 | fun basic() { 32 | val xml = default.encodeToString( 33 | Greeting( 34 | from = "Ryan", 35 | to = "Bill", 36 | message = Message("Hi") 37 | ) 38 | ) 39 | val expected = """ 40 | Hi 41 | """.trimIndent() 42 | assertEquals(expected, xml) 43 | } 44 | 45 | @Test 46 | fun defaultNamespaces() { 47 | @Serializable 48 | @SerialName("DefaultNamespace") 49 | @XmlDefaultNamespace("http://example.com/entity") 50 | data class DefaultNamespace( 51 | @XmlAttribute val foo: String = "fooz", 52 | @XmlAttribute val bar: String = "barz", 53 | ) 54 | 55 | val actual = default.encodeToString(DefaultNamespace()) 56 | val expected = """ 57 | 58 | """.trimIndent() 59 | assertEquals(expected, actual) 60 | } 61 | 62 | @Test 63 | fun simpleAttributes() { 64 | @Serializable 65 | @SerialName("SimpleAttributes") 66 | data class SimpleAttributes( 67 | @XmlAttribute val first: String = "string", 68 | @XmlAttribute val second: Int = 1, 69 | @XmlAttribute val third: Float = 4.32f, 70 | @XmlAttribute val fourth: Double = 1.23, 71 | @XmlAttribute val fifth: Long = 123L, 72 | @XmlAttribute val sixth: Boolean = false, 73 | @XmlAttribute val seventh: Boolean = true, 74 | ) 75 | 76 | val actual = default.encodeToString(SimpleAttributes()) 77 | val expected = 78 | """""" 79 | assertEquals(expected, actual) 80 | } 81 | 82 | @Test 83 | fun contentEncodedAsText() { 84 | @Serializable 85 | @SerialName("ContentAsText") 86 | data class ContentAsText( 87 | @XmlAttribute val first: String = "one", 88 | @XmlAttribute val second: String = "two", 89 | @XmlContent val third: String = "three", 90 | val fourth: String = "four", 91 | ) 92 | 93 | val actual = default.encodeToString(ContentAsText()) 94 | val expected = 95 | """threefour""" 96 | assertEquals(expected, actual) 97 | } 98 | 99 | @Test 100 | fun encodesDefaultNamespaces() { 101 | @Serializable 102 | @SerialName("stream") 103 | @XmlNamespace("http://etherx.jabber.org/streams", "stream") 104 | @XmlDefaultNamespace("jabber:client") 105 | data class Stream( 106 | @XmlAttribute val from: String = "me@jabber.im", 107 | @XmlAttribute val to: String = "jabber.im", 108 | @XmlAttribute val version: String = "1.0", 109 | @XmlAttribute val lang: String = "en", 110 | ) 111 | 112 | val actual = default.encodeToString(Stream()) 113 | val expected = """""" 114 | assertEquals(expected, actual) 115 | } 116 | 117 | // @Test 118 | // fun encodesMultipleNamespaces() { 119 | // @Serializable 120 | // @SerialName("stream") 121 | // @XmlNamespace("http://etherx.jabber.org/streams", "stream") 122 | // @XmlNamespace("jabber:client") 123 | // data class Stream( 124 | // @XmlAttribute val from: String = "me@jabber.im", 125 | // @XmlAttribute val to: String = "jabber.im", 126 | // @XmlAttribute val version: String = "1.0", 127 | // @XmlAttribute val lang: String = "en", 128 | // ) 129 | // 130 | // val actual = default.encodeToString(Stream()) 131 | // val expected = """""" 132 | // assertEquals(expected, actual) 133 | // } 134 | } 135 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/com/ryanharter/kotlinx/serialization/xml/XmlLexerTest.kt: -------------------------------------------------------------------------------- 1 | package com.ryanharter.kotlinx.serialization.xml 2 | 3 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer 4 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token 5 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token.AttributeName 6 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token.AttributeValue 7 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token.DocumentEnd 8 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token.ElementEnd 9 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token.ElementStart 10 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token.ElementStartEnd 11 | import com.ryanharter.kotlinx.serialization.xml.internal.XmlLexer.Token.Text 12 | import kotlin.test.Test 13 | import kotlin.test.assertEquals 14 | 15 | class XmlLexerTest { 16 | 17 | @Test 18 | fun skipsXmlDecl() { 19 | XmlLexer( 20 | """ 21 | 22 | 23 | """.trimIndent() 24 | ) 25 | .expectNextTokens( 26 | ElementStart("foo"), 27 | ElementStartEnd, 28 | ElementEnd("foo"), 29 | DocumentEnd, 30 | ) 31 | } 32 | 33 | @Test 34 | fun skipsDoctype() { 35 | XmlLexer( 36 | """ 37 | 38 | 39 | """.trimIndent() 40 | ) 41 | .expectNextTokens( 42 | ElementStart("foo"), 43 | ElementStartEnd, 44 | ElementEnd("foo"), 45 | DocumentEnd, 46 | ) 47 | } 48 | 49 | @Test 50 | fun skipsComments() { 51 | XmlLexer( 52 | """ 53 | 54 | 55 | 56 | 57 | """.trimIndent() 58 | ) 59 | .expectNextTokens( 60 | ElementStart("foo"), 61 | ElementStartEnd, 62 | ElementEnd("foo"), 63 | DocumentEnd, 64 | ) 65 | } 66 | 67 | @Test 68 | fun element() { 69 | XmlLexer("""""") 70 | .expectNextTokens( 71 | ElementStart("foo"), 72 | ElementStartEnd, 73 | ElementEnd("foo"), 74 | DocumentEnd, 75 | ) 76 | } 77 | 78 | @Test 79 | fun elementWithTextContent() { 80 | XmlLexer("""some text""") 81 | .expectNextTokens( 82 | ElementStart("foo"), 83 | ElementStartEnd, 84 | Text("some text"), 85 | ElementEnd("foo"), 86 | DocumentEnd, 87 | ) 88 | } 89 | 90 | @Test 91 | fun elementWithCDATA() { 92 | XmlLexer("""some text with in it""") 93 | .expectNextTokens( 94 | ElementStart("foo"), 95 | ElementStartEnd, 96 | Text("some text with cdata text that has w3!rd ch""") 105 | .expectNextTokens( 106 | ElementStart("foo"), 107 | AttributeName("attribute"), 108 | AttributeValue("bar"), 109 | ElementStartEnd, 110 | ElementEnd("foo"), 111 | DocumentEnd, 112 | ) 113 | } 114 | 115 | @Test 116 | fun elementWithAttributes() { 117 | XmlLexer("""""") 118 | .expectNextTokens( 119 | ElementStart("foo"), 120 | AttributeName("first"), 121 | AttributeValue("bar"), 122 | AttributeName("second"), 123 | AttributeValue("baz"), 124 | ElementStartEnd, 125 | ElementEnd("foo"), 126 | DocumentEnd, 127 | ) 128 | } 129 | 130 | @Test 131 | fun selfClosingElement() { 132 | XmlLexer("""""") 133 | .expectNextTokens( 134 | ElementStart("foo"), 135 | ElementEnd(), 136 | DocumentEnd, 137 | ) 138 | } 139 | 140 | @Test 141 | fun selfClosingElementWithSpace() { 142 | XmlLexer("""""") 143 | .expectNextTokens( 144 | ElementStart("foo"), 145 | ElementEnd(), 146 | DocumentEnd, 147 | ) 148 | } 149 | 150 | @Test 151 | fun selfClosingElementWithAttribute() { 152 | XmlLexer("""""") 153 | .expectNextTokens( 154 | ElementStart("foo"), 155 | AttributeName("attribute"), 156 | AttributeValue("bar"), 157 | ElementEnd(), 158 | DocumentEnd, 159 | ) 160 | } 161 | 162 | @Test 163 | fun selfClosingElementWithAttributes() { 164 | XmlLexer("""""") 165 | .expectNextTokens( 166 | ElementStart("foo"), 167 | AttributeName("first"), 168 | AttributeValue("bar"), 169 | AttributeName("second"), 170 | AttributeValue("baz"), 171 | ElementEnd(), 172 | DocumentEnd, 173 | ) 174 | } 175 | 176 | @Test 177 | fun nestedElements() { 178 | XmlLexer("""text""") 179 | .expectNextTokens( 180 | ElementStart("foo"), 181 | ElementStartEnd, 182 | ElementStart("bar"), 183 | AttributeName("first"), 184 | AttributeValue("baz"), 185 | ElementStartEnd, 186 | Text("text"), 187 | ElementEnd("bar"), 188 | ElementEnd("foo"), 189 | DocumentEnd, 190 | ) 191 | } 192 | 193 | @Test 194 | fun nestedElementsMixedWithContent() { 195 | XmlLexer( 196 | """ 197 | 198 | Text Content 199 | 200 | 201 | """.trimIndent() 202 | ).expectNextTokens( 203 | ElementStart("foo"), 204 | ElementStartEnd, 205 | Text("Text Content"), 206 | ElementStart("bar"), 207 | AttributeName("first"), 208 | AttributeValue("baz"), 209 | ElementEnd(), 210 | ElementEnd("foo"), 211 | DocumentEnd, 212 | ) 213 | } 214 | 215 | private fun XmlLexer.expectNextTokens(vararg expected: Token) { 216 | expected.forEach { 217 | assertEquals(it, readNextToken()) 218 | } 219 | } 220 | } --------------------------------------------------------------------------------