diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..a798a08
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+max_line_length = 110
+tab_width = 4
+trim_trailing_whitespace = true
+
+[{*.yml,*.json,gradlew}]
+indent_size = 2
+
+[{*.yml,*.md,*.txt}]
+max_line_length = off
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..3ae2c07
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,123 @@
+name: Build & Test
+
+on:
+ push:
+ branches:
+ - main
+ tags:
+ - '*'
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ build:
+ name: Build & Test
+ runs-on: macos-latest
+ steps:
+ - name: Checkout workspace
+ uses: actions/checkout@v3
+ timeout-minutes: 5
+ with:
+ fetch-depth: 0 # no shallow clones for SonarQube
+ - name: Set up Java JDK 17
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: '17.0.5+8'
+ - name: Gradle Cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ ~/.gradle/native
+ key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'build.gradle.kts') }}
+ restore-keys: ${{ runner.os }}-gradle-
+ - name: Kotlin Native Cache
+ uses: actions/cache@v3
+ with:
+ path: ~/.konan
+ key: ${{ runner.os }}-konan-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'build.gradle.kts') }}
+ restore-keys: ${{ runner.os }}-konan-
+ - name: Android Cache
+ uses: actions/cache@v3
+ with:
+ path: ~/.android
+ key: ${{ runner.os }}-android-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'build.gradle.kts') }}
+ restore-keys: ${{ runner.os }}-android-
+ - name: Cache SonarCloud packages
+ uses: actions/cache@v1
+ with:
+ path: ~/.sonar/cache
+ key: ${{ runner.os }}-sonar
+ restore-keys: ${{ runner.os }}-sonar
+ - name: Build & Test
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ run: |
+ chmod +x ./gradlew
+ ./gradlew build test koverXmlReport detekt sonar assembleXCFramework --parallel
+ - name: Set RELEASE_VERSION variable
+ run: |
+ echo "RELEASE_VERSION=$(cat build/version.txt)" >> $GITHUB_ENV
+ echo ${{ env.RELEASE_VERSION }}
+ - name: Upload reports
+ uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: reports.zip
+ path: build/reports
+ - name: List files
+ run: |
+ ls -lah build/bin/*/releaseExecutable build/libs/ build/outputs/aar/ build/XCFrameworks/release/
+ - name: Upload xmpcore.jar
+ uses: actions/upload-artifact@v3
+ with:
+ if-no-files-found: error
+ name: xmpcore.jar
+ path: build/libs/xmpcore-jvm-${{ env.RELEASE_VERSION }}.jar
+ - name: Upload xmpcore.aar
+ uses: actions/upload-artifact@v3
+ with:
+ if-no-files-found: error
+ name: xmpcore.aar
+ path: build/outputs/aar/xmpcore-release.aar
+ - name: Upload xmpcore.xcframework
+ uses: actions/upload-artifact@v3
+ with:
+ if-no-files-found: error
+ name: xmpcore.xcframework
+ path: build/XCFrameworks/release/xmpcore.xcframework
+ - name: Upload xmpcore.exe
+ uses: actions/upload-artifact@v3
+ with:
+ if-no-files-found: error
+ name: xmpcore.exe
+ path: build/bin/win/releaseExecutable/xmpcore.exe
+ - name: Upload xmpcore-macosX64.kexe
+ uses: actions/upload-artifact@v3
+ with:
+ if-no-files-found: error
+ name: xmpcore-macosX64.kexe
+ path: build/bin/macosX64/releaseExecutable/xmpcore.kexe
+ - name: Upload xmpcore-macosArm64.kexe
+ uses: actions/upload-artifact@v3
+ with:
+ if-no-files-found: error
+ name: xmpcore-macosArm64.kexe
+ path: build/bin/macosArm64/releaseExecutable/xmpcore.kexe
+ - name: Export secring.pgp
+ if: startsWith(github.ref, 'refs/tags/') # Only for releases
+ run: |
+ echo ${{ secrets.SINGING_SECRET_KEY_RING_FILE_CONTENTS }} | base64 --decode > secring.pgp
+ - name: Publish to Maven Central
+ if: startsWith(github.ref, 'refs/tags/') # Only for releases
+ env:
+ SIGNING_ENABLED: true
+ SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
+ SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
+ OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
+ OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
+ run: ./gradlew publishAllPublicationsToSonatypeRepository
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5b27f24
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+**/.DS_Store
+**/Thumbs.db
+.DS_Store
+/.gradle/
+/.idea/.name
+/.idea/artifacts/
+/.idea/assetWizardSettings.xml
+/.idea/compiler.xml
+/.idea/deploymentTargetDropDown.xml
+/.idea/gradle.xml
+/.idea/jarRepositories.xml
+/.idea/kotlinScripting.xml
+/.idea/kotlinc.xml
+/.idea/libraries/
+/.idea/misc.xml
+/.idea/modules.xml
+/.idea/modules/
+/.idea/runConfigurations.xml
+/.idea/shelf/
+/.idea/sonarlint/
+/.idea/uiDesigner.xml
+/.idea/vcs.xml
+/.idea/workspace.xml
+/.idea/xmpcore.iml
+/build/
+/local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..8d78c13
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/detekt.xml b/.idea/detekt.xml
new file mode 100644
index 0000000..5ac0d84
--- /dev/null
+++ b/.idea/detekt.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ detekt.yml
+
+
\ No newline at end of file
diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml
new file mode 100644
index 0000000..6545923
--- /dev/null
+++ b/.idea/externalDependencies.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..35642cc
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,1581 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Publish_to_Maven_local.xml b/.idea/runConfigurations/Publish_to_Maven_local.xml
new file mode 100644
index 0000000..8ebb442
--- /dev/null
+++ b/.idea/runConfigurations/Publish_to_Maven_local.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6bad5ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# XMP Core for Kotlin Multiplatform
+
+[![Kotlin](https://img.shields.io/badge/kotlin-1.8.20-blue.svg?logo=kotlin)](httpw://kotlinlang.org)
+![JVM](https://img.shields.io/badge/-JVM-gray.svg?style=flat)
+![Android](https://img.shields.io/badge/-Android-gray.svg?style=flat)
+![macOS](https://img.shields.io/badge/-macOS-gray.svg?style=flat)
+![iOS](https://img.shields.io/badge/-iOS-gray.svg?style=flat)
+[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=xmpcore&metric=coverage)](https://sonarcloud.io/summary/new_code?id=xmpcore)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.ashampoo/xmpcore/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.ashampoo/xmpcore)
+
+This library is a port of Adobe's XMP SDK to Kotlin Multiplatform.
+
+It's part of [Ashampoo Photos](https://ashampoo.com/photos).
+
+## Installation
+
+```
+implementation("com.ashampoo:xmpcore:0.1.0")
+```
+
+## How to use
+
+The library has been designed as a drop-in replacement for users who previously
+employed XMP Core Java. Therefore, all the documentation applicable to the
+Java SDK also pertains to this library.
+However, please note that we have made the decision to remove the functionality for reading
+from and writing to ByteArray and InputStreams, as we believe it is unnecessary.
+
+### Sample code
+
+```
+val originalXmp: String = "... your XMP ..."
+
+val xmpMeta: XMPMeta = XMPMetaFactory.parseFromString(originalXmp)
+
+val xmpSerializeOptions =
+ SerializeOptions()
+ .setOmitXmpMetaElement(false)
+ .setOmitPacketWrapper(false)
+ .setUseCompactFormat(true)
+ .setSort(true)
+
+val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptions)
+```
+
+You find more samples in the unit tests.
+
+### Migration hint
+
+If you have previously utilized the official XMP Core Java library available on
+Maven Central, please make sure to update your imports from `com.adobe.internal.xmp`
+to `com.ashampoo.xmp`.
+
+## Acknowledgements
+
+* JetBrains for making [Kotlin](https://kotlinlang.org).
+* Adobe for making the XMP Core Java SDK.
+* Paul de Vrieze for making [XmlUtil](https://github.com/pdvrieze/xmlutil)
+
+## License
+
+The same [BSD license](original_source/original_license.txt) applies to this project as to Adobe's open source XMP SDK,
+from which it is derived.
+
+Note: The original license page went offline, but you can still find it on
+[archive.org](https://web.archive.org/web/20210616112605/https://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html).
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..e2be98e
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,423 @@
+import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
+
+plugins {
+ kotlin("multiplatform") version "1.8.20"
+ id("com.android.library") version "7.4.2"
+ id("maven-publish")
+ id("signing")
+ id("io.gitlab.arturbosch.detekt") version "1.22.0"
+ id("org.sonarqube") version "4.0.0.2929"
+ id("org.jetbrains.kotlinx.kover") version "0.6.1"
+ id("com.asarkar.gradle.build-time-tracker") version "4.3.0"
+ id("me.qoomon.git-versioning") version "6.4.1"
+ id("com.goncalossilva.resources") version "0.3.2"
+}
+
+repositories {
+ google()
+ mavenCentral()
+}
+
+val productName = "Ashampoo XMP Core"
+
+val ktorVersion: String = "2.3.2"
+val xmlUtilVersion: String = "0.86.1"
+
+description = productName
+group = "com.ashampoo"
+version = "0.0.0"
+
+gitVersioning.apply {
+
+ refs {
+ /* Main branch contains the current dev version */
+ branch("main") {
+ version = "\${commit.short}"
+ }
+ /* Release / tags have real version numbers */
+ tag("v(?.*)") {
+ version = "\${ref.version}"
+ }
+ }
+
+ /* Fallback if branch was not found (for feature branches) */
+ rev {
+ version = "\${commit.short}"
+ }
+}
+
+apply(plugin = "io.gitlab.arturbosch.detekt")
+apply(plugin = "org.sonarqube")
+apply(plugin = "kover")
+
+buildTimeTracker {
+ sortBy.set(com.asarkar.gradle.buildtimetracker.Sort.DESC)
+}
+
+sonar {
+ properties {
+
+ property("sonar.projectKey", "xmpcore")
+ property("sonar.projectName", productName)
+ property("sonar.organization", "realashampoo")
+ property("sonar.host.url", "https://sonarcloud.io")
+
+ property(
+ "sonar.sources",
+ listOf(
+ "./src/commonMain/kotlin",
+ "./src/posixMain/kotlin"
+ )
+ )
+ property(
+ "sonar.tests",
+ listOf(
+ "./src/commonTest/kotlin"
+ )
+ )
+
+ /* Include Android Lint */
+ property("sonar.android.lint.report", "${project.buildDir}/reports/lint-results.xml")
+
+ /* Include Detekt issues */
+ val detektPath = "${project.buildDir}/reports/detekt/detekt.xml"
+ println("Detekt report: $detektPath")
+ property("sonar.kotlin.detekt.reportPaths", detektPath)
+
+ /* Include Kover code coverage */
+ val koverPath = "${project.buildDir}/reports/kover/xml/report.xml"
+ println("Kover report: $koverPath")
+ property("sonar.coverage.jacoco.xmlReportPaths", koverPath)
+ }
+}
+
+detekt {
+ source = files("src", "build.gradle.kts")
+ allRules = true
+ config = files("detekt.yml")
+ parallel = true
+ ignoreFailures = true
+ autoCorrect = true
+}
+
+kover {
+}
+
+koverMerged {
+ xmlReport {
+ }
+}
+
+dependencies {
+ detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.22.0")
+}
+
+kotlin {
+
+ android {
+
+ compilations.all {
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ }
+
+ publishLibraryVariants("release")
+ }
+
+ mingwX64("win") {
+ binaries {
+ executable(
+ buildTypes = setOf(NativeBuildType.RELEASE)
+ )
+ }
+ }
+
+ jvm {
+
+ java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ }
+
+ @Suppress("UnusedPrivateMember") // False positive
+ val commonMain by sourceSets.getting {
+
+ dependencies {
+
+ /* Needed for Charset class. */
+ /* Defined as api() to prevent problems when used from a pure-java project. */
+ api("io.ktor:ktor-io:$ktorVersion")
+
+ /* Needed to parse XML and create a DOM Document */
+ api("io.github.pdvrieze.xmlutil:core:$xmlUtilVersion")
+ api("io.github.pdvrieze.xmlutil:serialization:$xmlUtilVersion")
+ }
+ }
+
+ @Suppress("UnusedPrivateMember", "UNUSED_VARIABLE") // False positive
+ val commonTest by sourceSets.getting {
+ dependencies {
+
+ /* Kotlin Test */
+ implementation(kotlin("test"))
+
+ /* Multiplatform test resources */
+ implementation("com.goncalossilva:resources:0.3.2")
+
+ /* Multiplatform file access */
+ implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.2.0")
+ }
+ }
+
+ @Suppress("UnusedPrivateMember", "UNUSED_VARIABLE") // False positive
+ val jvmTest by sourceSets.getting {
+ dependencies {
+ implementation(kotlin("test-junit"))
+ }
+ }
+
+ val xcf = XCFramework()
+
+ listOf(
+ /* App Store */
+ iosArm64(),
+ /* Apple Silicon iOS Simulator */
+ iosSimulatorArm64(),
+ /* macOS Devices */
+ macosX64(),
+ macosArm64()
+ ).forEach {
+
+ it.binaries.executable(
+ buildTypes = setOf(NativeBuildType.RELEASE)
+ ) {
+ baseName = "xmpcore"
+ }
+
+ it.binaries.framework(
+ buildTypes = setOf(NativeBuildType.RELEASE)
+ ) {
+ baseName = "xmpcore"
+ /* Part of the XCFramework */
+ xcf.add(this)
+ }
+ }
+
+ // See https://youtrack.jetbrains.com/issue/KT-55751
+ val myAttribute = Attribute.of("KT-55751", String::class.java)
+
+ // replace releaseFrameworkIosFat by the name of the first configuration that conflicts
+ configurations.named("releaseFrameworkIosFat").configure {
+ attributes {
+ // put a unique attribute
+ attribute(myAttribute, "release-all")
+ }
+ }
+
+ // replace releaseFrameworkOsxFat by the name of the first configuration that conflicts
+ configurations.named("releaseFrameworkOsxFat").configure {
+ attributes {
+ // put a unique attribute
+ attribute(myAttribute, "release-all")
+ }
+ }
+
+ val jvmMain by sourceSets.getting
+
+ @Suppress("UnusedPrivateMember", "UNUSED_VARIABLE") // False positive
+ val androidMain by sourceSets.getting {
+ dependsOn(jvmMain)
+ }
+
+ val posixMain by sourceSets.creating {
+ dependsOn(commonMain)
+ }
+
+ @Suppress("UnusedPrivateMember", "UNUSED_VARIABLE") // False positive
+ val winMain by sourceSets.getting {
+ dependsOn(posixMain)
+ }
+
+ val iosArm64Main by sourceSets.getting
+ val iosSimulatorArm64Main by sourceSets.getting
+ val macosX64Main by sourceSets.getting
+ val macosArm64Main by sourceSets.getting
+
+ @Suppress("UnusedPrivateMember", "UNUSED_VARIABLE") // False positive
+ val appleMain by sourceSets.creating {
+
+ dependsOn(commonMain)
+ dependsOn(posixMain)
+
+ iosArm64Main.dependsOn(this)
+ iosSimulatorArm64Main.dependsOn(this)
+ macosX64Main.dependsOn(this)
+ macosArm64Main.dependsOn(this)
+ }
+}
+
+// region Writing version.txt for GitHub Actions
+val writeVersion = tasks.register("writeVersion") {
+ doLast {
+ File("build/version.txt").writeText(project.version.toString())
+ }
+}
+
+tasks.getByPath("build").finalizedBy(writeVersion)
+// endregion
+
+// region Android setup
+android {
+
+ namespace = "com.ashampoo.xmpcore"
+
+ compileSdk = 33
+
+ sourceSets["main"].res.srcDirs("src/commonMain/resources")
+
+ defaultConfig {
+ minSdk = 23
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.toVersion(11)
+ targetCompatibility = JavaVersion.toVersion(11)
+ }
+
+ publishing {
+ singleVariant("release") {
+ withSourcesJar()
+ withJavadocJar()
+ }
+ }
+}
+// endregion
+
+// region Maven publish
+
+ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
+ext["signing.password"] = System.getenv("SIGNING_PASSWORD")
+ext["signing.secretKeyRingFile"] = "secring.pgp"
+ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME")
+ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD")
+
+val javadocJar by tasks.registering(Jar::class) {
+ archiveClassifier.set("javadoc")
+}
+
+val signingEnabled: Boolean = System.getenv("SIGNING_ENABLED")?.toBoolean() ?: false
+
+afterEvaluate {
+
+ if (signingEnabled) {
+
+ /*
+ * Explicitly configure that signing comes before publishing.
+ * Otherwise the task execution of "publishAllPublicationsToSonatypeRepository" will fail.
+ */
+
+ val signJvmPublication by tasks.getting
+ val signAndroidReleasePublication by tasks.getting
+ val signIosArm64Publication by tasks.getting
+ val signIosSimulatorArm64Publication by tasks.getting
+ val signMacosArm64Publication by tasks.getting
+ val signMacosX64Publication by tasks.getting
+ val signWinPublication by tasks.getting
+ val signKotlinMultiplatformPublication by tasks.getting
+
+ val publishJvmPublicationToSonatypeRepository by tasks.getting
+ val publishAndroidReleasePublicationToSonatypeRepository by tasks.getting
+ val publishIosArm64PublicationToSonatypeRepository by tasks.getting
+ val publishIosSimulatorArm64PublicationToSonatypeRepository by tasks.getting
+ val publishMacosArm64PublicationToSonatypeRepository by tasks.getting
+ val publishMacosX64PublicationToSonatypeRepository by tasks.getting
+ val publishWinPublicationToSonatypeRepository by tasks.getting
+ val publishKotlinMultiplatformPublicationToSonatypeRepository by tasks.getting
+ val publishAllPublicationsToSonatypeRepository by tasks.getting
+
+ val signTasks = listOf(
+ signJvmPublication, signAndroidReleasePublication,
+ signIosArm64Publication, signIosSimulatorArm64Publication,
+ signMacosArm64Publication, signMacosX64Publication,
+ signWinPublication, signKotlinMultiplatformPublication
+ )
+
+ val publishTasks = listOf(
+ publishJvmPublicationToSonatypeRepository,
+ publishAndroidReleasePublicationToSonatypeRepository,
+ publishIosArm64PublicationToSonatypeRepository,
+ publishIosSimulatorArm64PublicationToSonatypeRepository,
+ publishMacosArm64PublicationToSonatypeRepository,
+ publishMacosX64PublicationToSonatypeRepository,
+ publishWinPublicationToSonatypeRepository,
+ publishKotlinMultiplatformPublicationToSonatypeRepository,
+ publishAllPublicationsToSonatypeRepository
+ )
+
+ /* Each publish task depenends on every sign task. */
+ for (publishTask in publishTasks)
+ for (signTask in signTasks)
+ publishTask.dependsOn(signTask)
+ }
+}
+
+fun getExtraString(name: String) = ext[name]?.toString()
+
+publishing {
+ publications {
+
+ // Configure maven central repository
+ repositories {
+ maven {
+ name = "sonatype"
+ setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
+ credentials {
+ username = getExtraString("ossrhUsername")
+ password = getExtraString("ossrhPassword")
+ }
+ }
+ }
+
+ publications.withType {
+
+ artifact(javadocJar.get())
+
+ pom {
+
+ name.set(productName)
+ description.set("XMP Core for Kotlin Multiplatform")
+ url.set("https://github.com/Ashampoo/xmpcore")
+
+ licenses {
+ license {
+ name.set("The BSD License")
+ url.set("http://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html")
+ }
+ }
+
+ developers {
+ developer {
+ name.set("Ashampoo GmbH & Co. KG")
+ url.set("https://www.ashampoo.com/")
+ }
+ }
+
+ scm {
+ connection.set("https://github.com/Ashampoo/xmpcore.git")
+ url.set("https://github.com/Ashampoo/xmpcore")
+ }
+ }
+ }
+
+ if (signingEnabled) {
+
+ signing {
+ sign(publishing.publications)
+ }
+ }
+ }
+}
+// endregion
diff --git a/detekt.yml b/detekt.yml
new file mode 100644
index 0000000..ed51aef
--- /dev/null
+++ b/detekt.yml
@@ -0,0 +1,931 @@
+# This file is based on the default rules and modified
+# https://github.com/detekt/detekt/blob/main/detekt-core/src/main/resources/default-detekt-config.yml
+# https://github.com/detekt/detekt/blob/main/detekt-formatting/src/main/resources/config/config.yml
+
+build:
+ maxIssues: 0
+ excludeCorrectable: false
+ weights:
+ # complexity: 2
+ # LongParameterList: 1
+ # style: 1
+ # comments: 1
+
+config:
+ validation: true
+ warningsAsErrors: false
+ checkExhaustiveness: true
+ excludes: ''
+
+processors:
+ active: true
+ exclude:
+ - 'DetektProgressListener'
+ # - 'KtFileCountProcessor'
+ # - 'PackageCountProcessor'
+ # - 'ClassCountProcessor'
+ # - 'FunctionCountProcessor'
+ # - 'PropertyCountProcessor'
+ # - 'ProjectComplexityProcessor'
+ # - 'ProjectCognitiveComplexityProcessor'
+ # - 'ProjectLLOCProcessor'
+ # - 'ProjectCLOCProcessor'
+ # - 'ProjectLOCProcessor'
+ # - 'ProjectSLOCProcessor'
+ # - 'LicenseHeaderLoaderExtension'
+
+console-reports:
+ active: true
+ exclude:
+ - 'ProjectStatisticsReport'
+ - 'ComplexityReport'
+ - 'NotificationReport'
+ - 'FindingsReport'
+ - 'FileBasedFindingsReport'
+ # - 'LiteFindingsReport'
+
+output-reports:
+ active: true
+ exclude:
+ # - 'TxtOutputReport'
+ # - 'XmlOutputReport'
+ # - 'HtmlOutputReport'
+ # - 'MdOutputReport'
+ # - 'SarifOutputReport'
+
+comments:
+ active: false
+ AbsentOrWrongFileLicense:
+ active: false
+ licenseTemplateFile: 'license.template'
+ licenseTemplateIsRegex: false
+ CommentOverPrivateFunction:
+ active: false # not our policy
+ CommentOverPrivateProperty:
+ active: false # not our policy
+ DeprecatedBlockTag:
+ active: true
+ EndOfSentenceFormat:
+ active: false # not important for us
+ endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
+ KDocReferencesNonPublicProperty:
+ active: false # not our policy
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ OutdatedDocumentation:
+ active: true
+ matchTypeParameters: true
+ matchDeclarationsOrder: true
+ allowParamOnConstructorProperties: false
+ UndocumentedPublicClass:
+ active: false # not our policy
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ searchInNestedClass: true
+ searchInInnerClass: true
+ searchInInnerObject: true
+ searchInInnerInterface: true
+ searchInProtectedClass: false
+ UndocumentedPublicFunction:
+ active: false # not our policy
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ searchProtectedFunction: false
+ UndocumentedPublicProperty:
+ active: false # not our policy
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ searchProtectedProperty: false
+
+complexity:
+ active: true
+ CognitiveComplexMethod:
+ active: false # already checked by SonarQube
+ threshold: 15
+ ComplexCondition:
+ active: true
+ threshold: 4
+ ComplexInterface:
+ active: true
+ threshold: 10
+ includeStaticDeclarations: false
+ includePrivateDeclarations: false
+ ignoreOverloaded: false
+ CyclomaticComplexMethod:
+ active: false # already checked by SonarQube
+ threshold: 15
+ ignoreSingleWhenExpression: false
+ ignoreSimpleWhenEntries: false
+ ignoreNestingFunctions: false
+ nestingFunctions:
+ - 'also'
+ - 'apply'
+ - 'forEach'
+ - 'isNotNull'
+ - 'ifNull'
+ - 'let'
+ - 'run'
+ - 'use'
+ - 'with'
+ LabeledExpression:
+ active: false # hard to replace
+ ignoredLabels: [ ]
+ LargeClass:
+ active: true
+ threshold: 600
+ LongMethod:
+ active: true
+ threshold: 100 # the default of 60 is quite short for Jetpack Compose
+ LongParameterList:
+ active: false # already checked by SonarQube
+ functionThreshold: 7 # 7 is SonarQube default
+ constructorThreshold: 7
+ ignoreDefaultParameters: false
+ ignoreDataClasses: true
+ ignoreAnnotatedParameter: [ ]
+ MethodOverloading:
+ active: true
+ threshold: 6
+ NamedArguments:
+ active: true
+ threshold: 3
+ ignoreArgumentsMatchingNames: false
+ NestedBlockDepth:
+ active: true
+ threshold: 4
+ NestedScopeFunctions:
+ active: true
+ threshold: 1
+ functions:
+ - 'kotlin.apply'
+ - 'kotlin.run'
+ - 'kotlin.with'
+ - 'kotlin.let'
+ - 'kotlin.also'
+ ReplaceSafeCallChainWithRun:
+ active: true
+ StringLiteralDuplication:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ threshold: 3
+ ignoreAnnotation: true
+ excludeStringsWithLessThan5Characters: true
+ ignoreStringsRegex: '$^'
+ TooManyFunctions:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ thresholdInFiles: 11
+ thresholdInClasses: 11
+ thresholdInInterfaces: 11
+ thresholdInObjects: 11
+ thresholdInEnums: 11
+ ignoreDeprecated: false
+ ignorePrivate: false
+ ignoreOverridden: false
+
+coroutines:
+ active: true
+ GlobalCoroutineUsage:
+ active: true
+ InjectDispatcher:
+ active: true
+ dispatcherNames:
+ - 'IO'
+ - 'Default'
+ - 'Unconfined'
+ RedundantSuspendModifier:
+ active: true
+ SleepInsteadOfDelay:
+ active: true
+ SuspendFunWithCoroutineScopeReceiver:
+ active: true
+ SuspendFunWithFlowReturnType:
+ active: true
+
+empty-blocks:
+ active: true
+ EmptyCatchBlock:
+ active: true
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ EmptyClassBlock:
+ active: true
+ EmptyDefaultConstructor:
+ active: true
+ EmptyDoWhileBlock:
+ active: true
+ EmptyElseBlock:
+ active: true
+ EmptyFinallyBlock:
+ active: true
+ EmptyForBlock:
+ active: true
+ EmptyFunctionBlock:
+ active: true
+ ignoreOverridden: false
+ EmptyIfBlock:
+ active: true
+ EmptyInitBlock:
+ active: true
+ EmptyKtFile:
+ active: true
+ EmptySecondaryConstructor:
+ active: true
+ EmptyTryBlock:
+ active: true
+ EmptyWhenBlock:
+ active: true
+ EmptyWhileBlock:
+ active: true
+
+exceptions:
+ active: true
+ ExceptionRaisedInUnexpectedLocation:
+ active: true
+ methodNames:
+ - 'equals'
+ - 'finalize'
+ - 'hashCode'
+ - 'toString'
+ InstanceOfCheckForException:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ NotImplementedDeclaration:
+ active: true
+ ObjectExtendsThrowable:
+ active: true
+ PrintStackTrace:
+ active: true
+ RethrowCaughtException:
+ active: true
+ ReturnFromFinally:
+ active: true
+ ignoreLabeled: false
+ SwallowedException:
+ active: true
+ ignoredExceptionTypes:
+ - 'InterruptedException'
+ - 'MalformedURLException'
+ - 'NumberFormatException'
+ - 'ParseException'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ ThrowingExceptionFromFinally:
+ active: true
+ ThrowingExceptionInMain:
+ active: true
+ ThrowingExceptionsWithoutMessageOrCause:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ exceptions:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Exception'
+ - 'IllegalArgumentException'
+ - 'IllegalMonitorStateException'
+ - 'IllegalStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ ThrowingNewInstanceOfSameException:
+ active: true
+ TooGenericExceptionCaught:
+ active: false # Sometimes this is just what we want to do.
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ exceptionNames:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Error'
+ - 'Exception'
+ - 'IllegalMonitorStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
+ TooGenericExceptionThrown:
+ active: true
+ exceptionNames:
+ - 'Error'
+ - 'Exception'
+ - 'RuntimeException'
+ - 'Throwable'
+
+naming:
+ active: true
+ BooleanPropertyNaming:
+ active: true
+ allowedPattern: '^(is|has|are)'
+ ignoreOverridden: true
+ ClassNaming:
+ active: true
+ classPattern: '[A-Z][a-zA-Z0-9]*'
+ ConstructorParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ privateParameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ EnumNaming:
+ active: true
+ enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
+ ForbiddenClassName:
+ active: true
+ forbiddenName: [ ]
+ FunctionMaxLength:
+ active: false # not useful
+ maximumFunctionNameLength: 30
+ FunctionMinLength:
+ active: false # not useful
+ minimumFunctionNameLength: 3
+ FunctionNaming:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ functionPattern: '[a-z][a-zA-Z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ ignoreAnnotated: [ 'Composable' ]
+ FunctionParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+ InvalidPackageDeclaration:
+ active: true
+ rootPackage: ''
+ requireRootInDeclaration: false
+ LambdaParameterNaming:
+ active: true
+ parameterPattern: '[a-z][A-Za-z0-9]*|_'
+ MatchingDeclarationName:
+ active: true
+ mustBeFirst: true
+ MemberNameEqualsClassName:
+ active: true
+ ignoreOverridden: true
+ NoNameShadowing:
+ active: true
+ NonBooleanPropertyPrefixedWithIs:
+ active: true
+ ObjectPropertyNaming:
+ active: true
+ constantPattern: '[A-Za-z][_A-Za-z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
+ PackageNaming:
+ active: true
+ packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
+ TopLevelPropertyNaming:
+ active: true
+ constantPattern: '[A-Z][_A-Z0-9]*'
+ propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
+ privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
+ VariableMaxLength:
+ active: true
+ maximumVariableNameLength: 64
+ VariableMinLength:
+ active: true
+ minimumVariableNameLength: 1
+ VariableNaming:
+ active: true
+ variablePattern: '[a-z][A-Za-z0-9]*'
+ privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
+ excludeClassPattern: '$^'
+ ignoreOverridden: true
+
+performance:
+ active: true
+ ArrayPrimitive:
+ active: true
+ CouldBeSequence:
+ active: true
+ threshold: 3
+ ForEachOnRange:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ SpreadOperator:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ UnnecessaryPartOfBinaryExpression:
+ active: true
+ UnnecessaryTemporaryInstantiation:
+ active: true
+
+potential-bugs:
+ active: true
+ AvoidReferentialEquality:
+ active: true
+ forbiddenTypePatterns:
+ - 'kotlin.String'
+ CastToNullableType:
+ active: true
+ Deprecation:
+ active: true
+ DontDowncastCollectionTypes:
+ active: true
+ DoubleMutabilityForCollection:
+ active: true
+ mutableTypes:
+ - 'kotlin.collections.MutableList'
+ - 'kotlin.collections.MutableMap'
+ - 'kotlin.collections.MutableSet'
+ - 'java.util.ArrayList'
+ - 'java.util.LinkedHashSet'
+ - 'java.util.HashSet'
+ - 'java.util.LinkedHashMap'
+ - 'java.util.HashMap'
+ ElseCaseInsteadOfExhaustiveWhen:
+ active: true
+ EqualsAlwaysReturnsTrueOrFalse:
+ active: true
+ EqualsWithHashCodeExist:
+ active: true
+ ExitOutsideMain:
+ active: true
+ ExplicitGarbageCollectionCall:
+ active: true
+ HasPlatformType:
+ active: true
+ IgnoredReturnValue:
+ active: true
+ restrictToConfig: true
+ returnValueAnnotations:
+ - '*.CheckResult'
+ - '*.CheckReturnValue'
+ ignoreReturnValueAnnotations:
+ - '*.CanIgnoreReturnValue'
+ returnValueTypes:
+ - 'kotlin.sequences.Sequence'
+ - 'kotlinx.coroutines.flow.*Flow'
+ - 'java.util.stream.*Stream'
+ ignoreFunctionCall: [ ]
+ ImplicitDefaultLocale:
+ active: true
+ ImplicitUnitReturnType:
+ active: true
+ allowExplicitReturnType: true
+ InvalidRange:
+ active: true
+ IteratorHasNextCallsNextMethod:
+ active: true
+ IteratorNotThrowingNoSuchElementException:
+ active: true
+ LateinitUsage:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ ignoreOnClassesPattern: ''
+ MapGetWithNotNullAssertionOperator:
+ active: true
+ MissingPackageDeclaration:
+ active: true
+ excludes: [ '**/*.kts', '**/main.kt' ]
+ NullCheckOnMutableProperty:
+ active: true
+ NullableToStringCall:
+ active: true
+ UnconditionalJumpStatementInLoop:
+ active: true
+ UnnecessaryNotNullCheck:
+ active: true
+ UnnecessaryNotNullOperator:
+ active: true
+ UnnecessarySafeCall:
+ active: true
+ UnreachableCatchBlock:
+ active: true
+ UnreachableCode:
+ active: true
+ UnsafeCallOnNullableType:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**' ]
+ UnsafeCast:
+ active: true
+ UnusedUnaryOperator:
+ active: true
+ UselessPostfixExpression:
+ active: true
+ WrongEqualsTypeParameter:
+ active: true
+
+style:
+ active: true
+ AlsoCouldBeApply:
+ active: true
+ CanBeNonNullable:
+ active: true
+ CascadingCallWrapping:
+ active: true
+ includeElvis: true
+ ClassOrdering:
+ active: true
+ CollapsibleIfStatements:
+ active: true
+ DataClassContainsFunctions:
+ active: false # allowed, be careful
+ conversionFunctionPrefix:
+ - 'to'
+ DataClassShouldBeImmutable:
+ active: true
+ DestructuringDeclarationWithTooManyEntries:
+ active: true
+ maxDestructuringEntries: 3
+ EqualsNullCall:
+ active: true
+ EqualsOnSignatureLine:
+ active: true
+ ExplicitCollectionElementAccessMethod:
+ active: true
+ ExplicitItLambdaParameter:
+ active: true
+ ExpressionBodySyntax:
+ active: true
+ includeLineWrapping: false
+ ForbiddenComment:
+ active: true
+ values:
+ - 'FIXME:'
+ - 'STOPSHIP:'
+ - 'TODO:'
+ allowedPatterns: ''
+ customMessage: ''
+ ForbiddenImport:
+ active: true
+ imports: [ ]
+ forbiddenPatterns: ''
+ ForbiddenMethodCall:
+ active: true
+ methods:
+ - reason: 'print does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.print'
+ - reason: 'println does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.println'
+ ForbiddenSuppress:
+ active: true
+ rules: [ ]
+ ForbiddenVoid:
+ active: true
+ ignoreOverridden: false
+ ignoreUsageInGenerics: false
+ FunctionOnlyReturningConstant:
+ active: true
+ ignoreOverridableFunction: true
+ ignoreActualFunction: true
+ excludedFunctions: [ ]
+ LoopWithTooManyJumpStatements:
+ active: true
+ maxJumpCount: 1
+ MagicNumber:
+ active: true
+ excludes: [ '**/commonTest/**', '**/jvmTest/**', '**/*.kts' ]
+ ignoreNumbers:
+ - '-1'
+ - '0'
+ - '1'
+ - '2'
+ ignoreHashCodeFunction: true
+ ignorePropertyDeclaration: false
+ ignoreLocalVariableDeclaration: false
+ ignoreConstantDeclaration: true
+ ignoreCompanionObjectPropertyDeclaration: true
+ ignoreAnnotation: false
+ ignoreNamedArgument: true
+ ignoreEnums: false
+ ignoreRanges: false
+ ignoreExtensionFunctions: true
+ MandatoryBracesIfStatements:
+ active: false # not our policy
+ MandatoryBracesLoops:
+ active: false # not our policy
+ MaxChainedCallsOnSameLine:
+ active: true
+ maxChainedCalls: 5
+ MaxLineLength:
+ active: true
+ maxLineLength: 110
+ excludePackageStatements: true
+ excludeImportStatements: true
+ excludeCommentStatements: false
+ excludeRawStrings: true
+ MayBeConst:
+ active: true
+ ModifierOrder:
+ active: true
+ MultilineLambdaItParameter:
+ active: true
+ MultilineRawStringIndentation:
+ active: true
+ indentSize: 4
+ NestedClassesVisibility:
+ active: true
+ NewLineAtEndOfFile:
+ active: true
+ NoTabs:
+ active: true
+ NullableBooleanCheck:
+ active: true
+ ObjectLiteralToLambda:
+ active: true
+ OptionalAbstractKeyword:
+ active: true
+ OptionalUnit:
+ active: false # It's recommended to specify explicitly public & protected declaration types
+ OptionalWhenBraces:
+ active: false # should be decided by the dev if it's needed on multiline expressions
+ PreferToOverPairSyntax:
+ active: true
+ ProtectedMemberInFinalClass:
+ active: true
+ RedundantExplicitType:
+ active: true
+ RedundantHigherOrderMapUsage:
+ active: true
+ RedundantVisibilityModifierRule:
+ active: true
+ ReturnCount:
+ active: false # not our policy since we don't like nesting
+ max: 2
+ excludedFunctions:
+ - 'equals'
+ excludeLabeled: false
+ excludeReturnFromLambda: true
+ excludeGuardClauses: false
+ SafeCast:
+ active: true
+ SerialVersionUIDInSerializableClass:
+ active: true
+ SpacingBetweenPackageAndImports:
+ active: true
+ ThrowsCount:
+ active: true
+ max: 2
+ excludeGuardClauses: false
+ TrailingWhitespace:
+ active: true
+ TrimMultilineRawString:
+ active: true
+ UnderscoresInNumericLiterals:
+ active: true
+ acceptableLength: 4
+ allowNonStandardGrouping: false
+ UnnecessaryAbstractClass:
+ active: true
+ UnnecessaryAnnotationUseSiteTarget:
+ active: true
+ UnnecessaryApply:
+ active: true
+ UnnecessaryBackticks:
+ active: true
+ UnnecessaryFilter:
+ active: true
+ UnnecessaryInheritance:
+ active: true
+ UnnecessaryInnerClass:
+ active: true
+ UnnecessaryLet:
+ active: true
+ UnnecessaryParentheses:
+ active: true
+ allowForUnclearPrecedence: false
+ UntilInsteadOfRangeTo:
+ active: true
+ UnusedImports:
+ active: true
+ UnusedPrivateClass:
+ active: true
+ UnusedPrivateMember:
+ active: true
+ allowedNames: '(_|ignored|expected|serialVersionUID)'
+ UseAnyOrNoneInsteadOfFind:
+ active: true
+ UseArrayLiteralsInAnnotations:
+ active: true
+ UseCheckNotNull:
+ active: true
+ UseCheckOrError:
+ active: true
+ UseDataClass:
+ active: true
+ allowVars: false
+ UseEmptyCounterpart:
+ active: true
+ UseIfEmptyOrIfBlank:
+ active: true
+ UseIfInsteadOfWhen:
+ active: true
+ UseIsNullOrEmpty:
+ active: true
+ UseOrEmpty:
+ active: true
+ UseRequire:
+ active: true
+ UseRequireNotNull:
+ active: true
+ UseSumOfInsteadOfFlatMapSize:
+ active: true
+ UselessCallOnNotNull:
+ active: true
+ UtilityClassWithPublicConstructor:
+ active: true
+ VarCouldBeVal:
+ active: true
+ ignoreLateinitVar: false
+ WildcardImport:
+ active: true
+ excludeImports:
+ - 'java.util.*'
+
+formatting:
+ active: true
+ android: false
+ autoCorrect: true
+ AnnotationOnSeparateLine:
+ active: true
+ autoCorrect: true
+ AnnotationSpacing:
+ active: true
+ autoCorrect: true
+ ArgumentListWrapping:
+ active: false # strange results sometimes
+ autoCorrect: true
+ indentSize: 4
+ maxLineLength: 110
+ BlockCommentInitialStarAlignment:
+ active: true
+ autoCorrect: true
+ ChainWrapping:
+ active: true
+ autoCorrect: true
+ CommentSpacing:
+ active: true
+ autoCorrect: true
+ CommentWrapping:
+ active: true
+ autoCorrect: true
+ indentSize: 4
+ DiscouragedCommentLocation:
+ active: true
+ autoCorrect: true
+ EnumEntryNameCase:
+ active: true
+ autoCorrect: true
+ Filename:
+ active: false # looks odd, not sure about this rule
+ FinalNewline:
+ active: true
+ autoCorrect: true
+ insertFinalNewLine: true
+ FunKeywordSpacing:
+ active: true
+ autoCorrect: true
+ FunctionReturnTypeSpacing:
+ active: true
+ autoCorrect: true
+ FunctionSignature:
+ active: false # has some false positives in Detekt 0.22
+ autoCorrect: true
+ forceMultilineWhenParameterCountGreaterOrEqualThan: 3
+ functionBodyExpressionWrapping: 'default'
+ maxLineLength: 80
+ indentSize: 4
+ FunctionStartOfBodySpacing:
+ active: true
+ autoCorrect: true
+ FunctionTypeReferenceSpacing:
+ active: true
+ autoCorrect: true
+ ImportOrdering:
+ active: true
+ autoCorrect: true
+ layout: '*,java.**,javax.**,kotlin.**,^'
+ Indentation:
+ active: true
+ autoCorrect: true
+ indentSize: 4
+ KdocWrapping:
+ active: true
+ autoCorrect: true
+ indentSize: 4
+ MaximumLineLength:
+ active: true
+ maxLineLength: 110
+ ignoreBackTickedIdentifier: false
+ ModifierListSpacing:
+ active: true
+ autoCorrect: true
+ ModifierOrdering:
+ active: true
+ autoCorrect: true
+ MultiLineIfElse:
+ active: false # not our policy
+ autoCorrect: true
+ NoBlankLineBeforeRbrace:
+ active: false # bad style
+ autoCorrect: true
+ NoBlankLinesInChainedMethodCalls:
+ active: true
+ autoCorrect: true
+ NoConsecutiveBlankLines:
+ active: true
+ autoCorrect: true
+ NoEmptyClassBody:
+ active: true
+ autoCorrect: true
+ NoEmptyFirstLineInMethodBlock:
+ active: false # bad style
+ autoCorrect: true
+ NoLineBreakAfterElse:
+ active: true
+ autoCorrect: true
+ NoLineBreakBeforeAssignment:
+ active: true
+ autoCorrect: true
+ NoMultipleSpaces:
+ active: true
+ autoCorrect: true
+ NoSemicolons:
+ active: true
+ autoCorrect: true
+ NoTrailingSpaces:
+ active: true
+ autoCorrect: true
+ NoUnitReturn:
+ active: true
+ autoCorrect: true
+ NoUnusedImports:
+ active: true
+ autoCorrect: true
+ NoWildcardImports:
+ active: true
+ packagesToUseImportOnDemandProperty: 'java.util.*,kotlinx.android.synthetic.**'
+ NullableTypeSpacing:
+ active: true
+ autoCorrect: true
+ PackageName:
+ active: true
+ autoCorrect: true
+ ParameterListSpacing:
+ active: true
+ autoCorrect: true
+ ParameterListWrapping:
+ active: true
+ autoCorrect: true
+ maxLineLength: 110
+ SpacingAroundAngleBrackets:
+ active: true
+ autoCorrect: true
+ SpacingAroundColon:
+ active: true
+ autoCorrect: true
+ SpacingAroundComma:
+ active: true
+ autoCorrect: true
+ SpacingAroundCurly:
+ active: true
+ autoCorrect: true
+ SpacingAroundDot:
+ active: true
+ autoCorrect: true
+ SpacingAroundDoubleColon:
+ active: true
+ autoCorrect: true
+ SpacingAroundKeyword:
+ active: true
+ autoCorrect: true
+ SpacingAroundOperators:
+ active: true
+ autoCorrect: true
+ SpacingAroundParens:
+ active: true
+ autoCorrect: true
+ SpacingAroundRangeOperator:
+ active: true
+ autoCorrect: true
+ SpacingAroundUnaryOperator:
+ active: true
+ autoCorrect: true
+ SpacingBetweenDeclarationsWithAnnotations:
+ active: true
+ autoCorrect: true
+ SpacingBetweenDeclarationsWithComments:
+ active: true
+ autoCorrect: true
+ SpacingBetweenFunctionNameAndOpeningParenthesis:
+ active: true
+ autoCorrect: true
+ StringTemplate:
+ active: true
+ autoCorrect: true
+ TrailingCommaOnCallSite:
+ active: false # looks ugly
+ autoCorrect: true
+ useTrailingCommaOnCallSite: true
+ TrailingCommaOnDeclarationSite:
+ active: false # looks ugly
+ autoCorrect: true
+ useTrailingCommaOnDeclarationSite: true
+ TypeArgumentListSpacing:
+ active: true
+ autoCorrect: true
+ TypeParameterListSpacing:
+ active: true
+ autoCorrect: true
+ UnnecessaryParenthesesBeforeTrailingLambda:
+ active: true
+ autoCorrect: true
+ Wrapping:
+ active: true
+ autoCorrect: true
+ indentSize: 4
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..4831294
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,8 @@
+android.useAndroidX=true
+kotlin.code.style=official
+kotlin.mpp.androidSourceSetLayoutVersion=2
+kotlin.mpp.enableCInteropCommonization=true
+kotlin.mpp.stability.nowarn=true
+org.gradle.caching=true
+org.gradle.jvmargs=-Xmx4g -Dkotlin.daemon.jvm.options\="-Xmx4g"
+org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..c1962a7
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..37aef8d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+networkTimeout=10000
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..aeb74cb
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,245 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/original_source/XMPCoreJava-5.1.3.zip b/original_source/XMPCoreJava-5.1.3.zip
new file mode 100644
index 0000000..3306412
Binary files /dev/null and b/original_source/XMPCoreJava-5.1.3.zip differ
diff --git a/original_source/original_license.png b/original_source/original_license.png
new file mode 100644
index 0000000..598ae00
Binary files /dev/null and b/original_source/original_license.png differ
diff --git a/original_source/original_license.txt b/original_source/original_license.txt
new file mode 100644
index 0000000..615718d
--- /dev/null
+++ b/original_source/original_license.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2009, Adobe Systems Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems Incorporated, nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANT ABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..3cd6dd3
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,9 @@
+rootProject.name = "xmpcore"
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ google()
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPConst.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPConst.kt
new file mode 100644
index 0000000..043e808
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPConst.kt
@@ -0,0 +1,287 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+/**
+ * Common constants for the XMP Toolkit.
+ */
+object XMPConst {
+
+ /**
+ * The XML namespace for XML.
+ */
+ const val NS_XML: String = "http://www.w3.org/XML/1998/namespace"
+
+ /**
+ * The XML namespace for RDF.
+ */
+ const val NS_RDF: String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
+ /**
+ * The XML namespace for the Dublin Core schema.
+ */
+ const val NS_DC: String = "http://purl.org/dc/elements/1.1/"
+
+ /**
+ * The XML namespace for the IPTC Core schema.
+ */
+ const val NS_IPTCCORE: String = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
+
+ /**
+ * The XML namespace for the IPTC Extension schema.
+ */
+ const val NS_IPTCEXT: String = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/"
+
+ /**
+ * The XML namespace for the DICOM medical schema.
+ */
+ const val NS_DICOM: String = "http://ns.adobe.com/DICOM/"
+
+ /**
+ * The XML namespace for the PLUS (Picture Licensing Universal System, http://www.useplus.org)
+ */
+ const val NS_PLUS: String = "http://ns.useplus.org/ldf/xmp/1.0/"
+
+ const val NS_MWG_RS: String = "http://www.metadataworkinggroup.com/schemas/regions/"
+
+ const val NS_ACDSEE: String = "http://ns.acdsee.com/iptc/1.0/"
+
+ // Adobe standard namespaces
+
+ /**
+ * The XML namespace Adobe XMP Metadata.
+ */
+ const val NS_X: String = "adobe:ns:meta/"
+
+ const val NS_IX: String = "http://ns.adobe.com/iX/1.0/"
+
+ /**
+ * The XML namespace for the XMP "basic" schema.
+ */
+ const val NS_XMP: String = "http://ns.adobe.com/xap/1.0/"
+
+ /**
+ * The XML namespace for the XMP copyright schema.
+ */
+ const val NS_XMP_RIGHTS: String = "http://ns.adobe.com/xap/1.0/rights/"
+
+ /**
+ * The XML namespace for the XMP digital asset management schema.
+ */
+ const val NS_XMP_MM: String = "http://ns.adobe.com/xap/1.0/mm/"
+
+ /**
+ * The XML namespace for the job management schema.
+ */
+ const val NS_XMP_BJ: String = "http://ns.adobe.com/xap/1.0/bj/"
+
+ /**
+ * The XML namespace for the job management schema.
+ */
+ const val NS_XMP_NOTE: String = "http://ns.adobe.com/xmp/note/"
+
+ /**
+ * The XML namespace for the PDF schema.
+ */
+ const val NS_PDF: String = "http://ns.adobe.com/pdf/1.3/"
+
+ /**
+ * The XML namespace for the PDF schema.
+ */
+ const val NS_PDFX: String = "http://ns.adobe.com/pdfx/1.3/"
+
+ const val NS_PDFX_ID: String = "http://www.npes.org/pdfx/ns/id/"
+
+ const val NS_PDFA_SCHEMA: String = "http://www.aiim.org/pdfa/ns/schema#"
+
+ const val NS_PDFA_PROPERTY: String = "http://www.aiim.org/pdfa/ns/property#"
+
+ const val NS_PDFA_TYPE: String = "http://www.aiim.org/pdfa/ns/type#"
+
+ const val NS_PDFA_FIELD: String = "http://www.aiim.org/pdfa/ns/field#"
+
+ const val NS_PDFA_ID: String = "http://www.aiim.org/pdfa/ns/id/"
+
+ const val NS_PDFA_EXTENSION: String = "http://www.aiim.org/pdfa/ns/extension/"
+
+ /**
+ * The XML namespace for the Photoshop custom schema.
+ */
+ const val NS_PHOTOSHOP: String = "http://ns.adobe.com/photoshop/1.0/"
+
+ /**
+ * The XML namespace for the Photoshop Album schema.
+ */
+ const val NS_PSALBUM: String = "http://ns.adobe.com/album/1.0/"
+
+ /**
+ * The XML namespace for Adobe's EXIF schema.
+ */
+ const val NS_EXIF: String = "http://ns.adobe.com/exif/1.0/"
+
+ /**
+ * NS for the CIPA XMP for Exif document v1.1
+ */
+ const val NS_EXIFX: String = "http://cipa.jp/exif/1.0/"
+
+ const val NS_EXIF_AUX: String = "http://ns.adobe.com/exif/1.0/aux/"
+
+ const val NS_TIFF: String = "http://ns.adobe.com/tiff/1.0/"
+
+ const val NS_PNG: String = "http://ns.adobe.com/png/1.0/"
+
+ const val NS_JPEG: String = "http://ns.adobe.com/jpeg/1.0/"
+
+ const val NS_JP2K: String = "http://ns.adobe.com/jp2k/1.0/"
+
+ const val NS_CAMERARAW: String = "http://ns.adobe.com/camera-raw-settings/1.0/"
+
+ const val NS_ADOBESTOCKPHOTO: String = "http://ns.adobe.com/StockPhoto/1.0/"
+
+ const val NS_CREATOR_ATOM: String = "http://ns.adobe.com/creatorAtom/1.0/"
+
+ const val NS_ASF: String = "http://ns.adobe.com/asf/1.0/"
+
+ const val NS_WAV: String = "http://ns.adobe.com/xmp/wav/1.0/"
+
+ /**
+ * BExt Schema
+ */
+ const val NS_BWF: String = "http://ns.adobe.com/bwf/bext/1.0/"
+
+ /**
+ * RIFF Info Schema
+ */
+ const val NS_RIFFINFO: String = "http://ns.adobe.com/riff/info/"
+
+ const val NS_SCRIPT: String = "http://ns.adobe.com/xmp/1.0/Script/"
+
+ /**
+ * Transform XMP
+ */
+ const val NS_TXMP: String = "http://ns.adobe.com/TransformXMP/"
+
+ /**
+ * Adobe Flash SWF
+ */
+ const val NS_SWF: String = "http://ns.adobe.com/swf/1.0/"
+
+ // XMP namespaces that are Adobe private
+
+ const val NS_DM: String = "http://ns.adobe.com/xmp/1.0/DynamicMedia/"
+
+ const val NS_TRANSIENT: String = "http://ns.adobe.com/xmp/transient/1.0/"
+
+ /**
+ * legacy Dublin Core NS, will be converted to NS_DC
+ */
+ const val NS_DC_DEPRECATED: String = "http://purl.org/dc/1.1/"
+
+ // XML namespace constants for qualifiers and structured property fields.
+
+ /**
+ * The XML namespace for qualifiers of the xmp:Identifier property.
+ */
+ const val TYPE_IDENTIFIERQUAL: String = "http://ns.adobe.com/xmp/Identifier/qual/1.0/"
+
+ /**
+ * The XML namespace for fields of the Dimensions type.
+ */
+ const val TYPE_DIMENSIONS: String = "http://ns.adobe.com/xap/1.0/sType/Dimensions#"
+
+ const val TYPE_TEXT: String = "http://ns.adobe.com/xap/1.0/t/"
+
+ const val TYPE_PAGEDFILE: String = "http://ns.adobe.com/xap/1.0/t/pg/"
+
+ const val TYPE_GRAPHICS: String = "http://ns.adobe.com/xap/1.0/g/"
+
+ /**
+ * The XML namespace for fields of a graphical image. Used for the Thumbnail type.
+ */
+ const val TYPE_IMAGE: String = "http://ns.adobe.com/xap/1.0/g/img/"
+
+ const val TYPE_FONT: String = "http://ns.adobe.com/xap/1.0/sType/Font#"
+
+ /**
+ * The XML namespace for fields of the ResourceEvent type.
+ */
+ const val TYPE_RESOURCEEVENT: String = "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
+
+ /**
+ * The XML namespace for fields of the ResourceRef type.
+ */
+ const val TYPE_RESOURCEREF: String = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
+
+ /**
+ * The XML namespace for fields of the Version type.
+ */
+ const val TYPE_ST_VERSION: String = "http://ns.adobe.com/xap/1.0/sType/Version#"
+
+ /**
+ * The XML namespace for fields of the JobRef type.
+ */
+ const val TYPE_ST_JOB: String = "http://ns.adobe.com/xap/1.0/sType/Job#"
+
+ const val TYPE_MANIFESTITEM: String = "http://ns.adobe.com/xap/1.0/sType/ManifestItem#"
+
+ // ---------------------------------------------------------------------------------------------
+ // Basic types and constants
+
+ /**
+ * The canonical true string value for Booleans in serialized XMP. Code that converts from the
+ * string to a bool should be case insensitive, and even allow "1".
+ */
+ const val TRUESTR: String = "True"
+
+ /**
+ * The canonical false string value for Booleans in serialized XMP. Code that converts from the
+ * string to a bool should be case insensitive, and even allow "0".
+ */
+ const val FALSESTR: String = "False"
+
+ /**
+ * Index that has the meaning to be always the last item in an array.
+ */
+ const val ARRAY_LAST_ITEM: Int = -1
+
+ /**
+ * Node name of an array item.
+ */
+ const val ARRAY_ITEM_NAME: String = "[]"
+
+ /**
+ * The x-default string for localized properties
+ */
+ const val X_DEFAULT: String = "x-default"
+
+ /**
+ * xml:lang qualfifier
+ */
+ const val XML_LANG: String = "xml:lang"
+
+ /**
+ * rdf:type qualfifier
+ */
+ const val RDF_TYPE: String = "rdf:type"
+
+ /**
+ * Processing Instruction (PI) for xmp packet
+ */
+ const val XMP_PI: String = "xpacket"
+
+ /**
+ * XMP meta tag version new
+ */
+ const val TAG_XMPMETA: String = "xmpmeta"
+
+ /**
+ * XMP meta tag version old
+ */
+ const val TAG_XAPMETA: String = "xapmeta"
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPError.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPError.kt
new file mode 100644
index 0000000..e61bfc3
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPError.kt
@@ -0,0 +1,28 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+interface XMPError {
+
+ companion object {
+ const val UNKNOWN: Int = 0
+ const val BADPARAM: Int = 4
+ const val BADVALUE: Int = 5
+ const val INTERNALFAILURE: Int = 9
+ const val BADSCHEMA: Int = 101
+ const val BADXPATH: Int = 102
+ const val BADOPTIONS: Int = 103
+ const val BADINDEX: Int = 104
+ const val BADSERIALIZE: Int = 107
+ const val BADXML: Int = 201
+ const val BADRDF: Int = 202
+ const val BADXMP: Int = 203
+ const val BADSTREAM: Int = 204
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPException.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPException.kt
new file mode 100644
index 0000000..c2542e3
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPException.kt
@@ -0,0 +1,18 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+/**
+ * This exception wraps all errors that occur in the XMP Toolkit.
+ */
+class XMPException(
+ message: String,
+ val errorCode: Int,
+ cause: Throwable? = null
+) : Exception(message, cause)
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPIterator.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPIterator.kt
new file mode 100644
index 0000000..d3959e2
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPIterator.kt
@@ -0,0 +1,77 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+import com.ashampoo.xmp.properties.XMPPropertyInfo
+
+/**
+ * Interface for the `XMPMeta` iteration services.
+ * `XMPIterator` provides a uniform means to iterate over the
+ * schema and properties within an XMP object.
+ *
+ * The iteration over the schema and properties within an XMP object is very
+ * complex. It is helpful to have a thorough understanding of the XMP data tree.
+ * One way to learn this is to create some complex XMP and examine the output of
+ * `XMPMeta#toString`. This is also described in the XMP
+ * Specification, in the XMP Data Model chapter.
+ *
+ * The top of the XMP data tree is a single root node. This does not explicitly
+ * appear in the dump and is never visited by an iterator (that is, it is never
+ * returned from `XMPIterator#next()`). Beneath the root are
+ * schema nodes. These are just collectors for top level properties in the same
+ * namespace. They are created and destroyed implicitly. Beneath the schema
+ * nodes are the property nodes. The nodes below a property node depend on its
+ * type (simple, struct, or array) and whether it has qualifiers.
+ *
+ * An `XMPIterator` is created by XMPMeta#interator() constructor
+ * defines a starting point for the iteration and options that control how it
+ * proceeds. By default the iteration starts at the root and visits all nodes
+ * beneath it in a depth first manner. The root node is not visited, the first
+ * visited node is a schema node. You can provide a schema name or property path
+ * to select a different starting node. By default this visits the named root
+ * node first then all nodes beneath it in a depth first manner.
+ *
+ * The `XMPIterator#next()` method delivers the schema URI, path,
+ * and option flags for the node being visited. If the node is simple it also
+ * delivers the value. Qualifiers for this node are visited next. The fields of
+ * a struct or items of an array are visited after the qualifiers of the parent.
+ *
+ * The options to control the iteration are:
+ *
+ * * JUST_CHILDREN - Visit just the immediate children of the root. Skip
+ * the root itself and all nodes below the immediate children. This omits the
+ * qualifiers of the immediate children, the qualifier nodes being below what
+ * they qualify, default is to visit the complete subtree.
+ * * JUST_LEAFNODES - Visit just the leaf property nodes and their
+ * qualifiers.
+ * * JUST_LEAFNAME - Return just the leaf component of the node names.
+ * The default is to return the full xmp path.
+ * * OMIT_QUALIFIERS - Do not visit the qualifiers.
+ * * INCLUDE_ALIASES - Adds known alias properties to the properties in the iteration.
+ * *Note:* Not supported in Java XMPCore!
+ *
+ * `next()` returns `XMPPropertyInfo`-objects and throws
+ * a `NoSuchElementException` if there are no more properties to
+ * return.
+ */
+interface XMPIterator : Iterator {
+
+ /**
+ * Skip the subtree below the current node when `next()` is
+ * called.
+ */
+ fun skipSubtree()
+
+ /**
+ * Skip the subtree below and remaining siblings of the current node when
+ * `next()` is called.
+ */
+ fun skipSiblings()
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPMeta.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPMeta.kt
new file mode 100644
index 0000000..4694f40
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPMeta.kt
@@ -0,0 +1,766 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+import com.ashampoo.xmp.options.IteratorOptions
+import com.ashampoo.xmp.options.ParseOptions
+import com.ashampoo.xmp.options.PropertyOptions
+import com.ashampoo.xmp.properties.XMPProperty
+
+/**
+ * This class represents the set of XMP metadata as a DOM representation. It has methods to read and
+ * modify all kinds of properties, create an iterator over all properties and serialize the metadata
+ * to a String, byte-array or `OutputStream`.
+ */
+@Suppress("ComplexInterface", "TooManyFunctions")
+interface XMPMeta {
+
+ // ---------------------------------------------------------------------------------------------
+ // Basic property manipulation functions
+
+ /**
+ * The property value getter-methods all take a property specification: the first two parameters
+ * are always the top level namespace URI (the "schema" namespace) and the basic name
+ * of the property being referenced. See the introductory discussion of path expression usage
+ * for more information.
+ *
+ * All the functions return an object inherited from `PropertyBase` or
+ * `null` if the property does not exist. The result object contains the value of
+ * the property and option flags describing the property. Arrays and the non-leaf levels of
+ * nodes do not have values.
+ *
+ * See [PropertyOptions] for detailed information about the options.
+ *
+ * This is the simplest property getter, mainly for top level simple properties or after using
+ * the path composition functions in XMPPathFactory.
+ *
+ * @param schemaNS The namespace URI for the property.
+ * The URI must be for a registered namespace.
+ * @param propName The name of the property. May be a general path expression,
+ * must not be `null` or the empty string.
+ * Using a namespace prefix on the first component is optional.
+ * If present without a schemaNS value then the prefix specifies the namespace.
+ * The prefix must be for a registered namespace.
+ * If both a schemaNS URI and propName prefix are present,
+ * they must be corresponding parts of a registered namespace.
+ * @return Returns a `XMPProperty` containing the value and the options or `null`
+ * if the property does not exist.
+ */
+ fun getProperty(schemaNS: String, propName: String): XMPProperty?
+
+ /**
+ * Provides access to items within an array. The index is passed as an integer, you need not
+ * worry about the path string syntax for array items, convert a loop index to a string, etc.
+ *
+ * @param schemaNS The namespace URI for the array. Has the same usage as in `getProperty()`.
+ * @param arrayName The name of the array. May be a general path expression,
+ * must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in `getProperty()`.
+ * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1.
+ * The constant [XMPConst.ARRAY_LAST_ITEM] always refers to the last
+ * existing array item.
+ * @return Returns a `XMPProperty` containing the value and the options or
+ * `null` if the property does not exist.
+ */
+ fun getArrayItem(schemaNS: String, arrayName: String, itemIndex: Int): XMPProperty?
+
+ /**
+ * Returns the number of items in the array.
+ *
+ * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+ * @param arrayName The name of the array. May be a general path expression,
+ * must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in `getProperty()`.
+ * @return Returns the number of items in the array.
+ */
+ fun countArrayItems(schemaNS: String, arrayName: String): Int
+
+ /**
+ * Provides access to fields within a nested structure. The namespace for the field is passed as
+ * a URI, you need not worry about the path string syntax.
+ *
+ * The names of fields should be XML qualified names, that is within an XML namespace. The path
+ * syntax for a qualified name uses the namespace prefix. This is unreliable since the prefix is
+ * never guaranteed. The URI is the formal name, the prefix is just a local shorthand in a given
+ * sequence of XML text.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+ * @param structName The name of the struct.
+ * May be a general path expression, must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in `getProperty()`.
+ * @param fieldNS The namespace URI for the field. Has the same URI and prefix usage as the
+ * schemaNS parameter.
+ * @param fieldName The name of the field. Must be a single XML name, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as the
+ * structName parameter.
+ * @return Returns a `XMPProperty` containing the value and the options or
+ * `null` if the property does not exist. Arrays and non-leaf levels of
+ * structs do not have values.
+ */
+ fun getStructField(
+ schemaNS: String,
+ structName: String,
+ fieldNS: String,
+ fieldName: String
+ ): XMPProperty?
+
+ /**
+ * Provides access to a qualifier attached to a property. The namespace for the qualifier is
+ * passed as a URI, you need not worry about the path string syntax. In many regards qualifiers
+ * are like struct fields. See the introductory discussion of qualified properties for more
+ * information.
+ *
+ * The names of qualifiers should be XML qualified names, that is within an XML namespace. The
+ * path syntax for a qualified name uses the namespace prefix. This is unreliable since the
+ * prefix is never guaranteed. The URI is the formal name, the prefix is just a local shorthand
+ * in a given sequence of XML text.
+ *
+ * *Note:* Qualifiers are only supported for simple leaf properties.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+ * @param structName The name of the struct.
+ * May be a general path expression, must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in `getProperty()`.
+ * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+ * schemaNS parameter.
+ * @param qualName The name of the qualifier. Must be a single XML name, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as the
+ * propName parameter.
+ * @return Returns a `XMPProperty` containing the value and the options of the
+ * qualifier or `null` if the property does not exist. The name of the
+ * qualifier must be a single XML name, must not be `null` or the empty
+ * string. Has the same namespace prefix usage as the propName parameter.
+ *
+ * The value of the qualifier is only set if it has one (Arrays and non-leaf levels of
+ * structs do not have values).
+ */
+ fun getQualifier(
+ schemaNS: String,
+ propName: String,
+ qualNS: String,
+ qualName: String
+ ): XMPProperty?
+
+ // ---------------------------------------------------------------------------------------------
+ // Functions for setting property values
+
+ /**
+ * The property value `setters` all take a property specification, their
+ * differences are in the form of this. The first two parameters are always the top level
+ * namespace URI (the `schema` namespace) and the basic name of the property being
+ * referenced. See the introductory discussion of path expression usage for more information.
+ *
+ * All of the functions take a string value for the property and option flags describing the
+ * property. The value must be Unicode in UTF-8 encoding. Arrays and non-leaf levels of structs
+ * do not have values. Empty arrays and structs may be created using appropriate option flags.
+ * All levels of structs that is assigned implicitly are created if necessary. appendArayItem
+ * implicitly creates the named array if necessary.
+ *
+ * See [PropertyOptions] for detailed information about the options.
+ *
+ * This is the simplest property setter, mainly for top level simple properties or after using
+ * the path composition functions in [XMPPathFactory].
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in getProperty.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @param propValue the value for the property (only leaf properties have a value).
+ * Arrays and non-leaf levels of structs do not have values.
+ * Must be `null` if the value is not relevant.
+ * The value is automatically detected: Boolean, Integer, Long, Double, XMPDateTime and
+ * byte[] are handled, on all other `toString()` is called.
+ * @param options Option flags describing the property. See the earlier description.
+ */
+ fun setProperty(
+ schemaNS: String,
+ propName: String,
+ propValue: Any?,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Replaces an item within an array. The index is passed as an integer, you need not worry about
+ * the path string syntax for array items, convert a loop index to a string, etc. The array
+ * passed must already exist. In normal usage the selected array item is modified. A new item is
+ * automatically appended if the index is the array size plus 1.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+ * @param arrayName The name of the array.
+ * May be a general path expression, must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in getProperty.
+ * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. To address
+ * the last existing item, use [XMPMeta.countArrayItems] to find
+ * out the length of the array.
+ * @param itemValue the new value of the array item. Has the same usage as propValue in
+ * `setProperty()`.
+ * @param options the set options for the item.
+ */
+ fun setArrayItem(
+ schemaNS: String,
+ arrayName: String,
+ itemIndex: Int,
+ itemValue: String,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Inserts an item into an array previous to the given index. The index is passed as an integer,
+ * you need not worry about the path string syntax for array items, convert a loop index to a
+ * string, etc. The array passed must already exist. In normal usage the selected array item is
+ * modified. A new item is automatically appended if the index is the array size plus 1.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+ * @param arrayName The name of the array.
+ * May be a general path expression, must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in getProperty.
+ * @param itemIndex The index to insert the new item. Arrays in XMP are indexed from 1. Use
+ * `XMPConst.ARRAY_LAST_ITEM` to append items.
+ * @param itemValue the new value of the array item. Has the same usage as
+ * propValue in `setProperty()`.
+ * @param options the set options that decide about the kind of the node.
+ */
+ fun insertArrayItem(
+ schemaNS: String,
+ arrayName: String,
+ itemIndex: Int,
+ itemValue: String,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Simplifies the construction of an array by not requiring that you pre-create an empty array.
+ * The array that is assigned is created automatically if it does not yet exist. Each call to
+ * appendArrayItem() appends an item to the array. The corresponding parameters have the same
+ * use as setArrayItem(). The arrayOptions parameter is used to specify what kind of array. If
+ * the array exists, it must have the specified form.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+ * @param arrayName The name of the array.
+ * May be a general path expression, must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in getProperty.
+ * @param arrayOptions Option flags describing the array form. The only valid options are
+ *
+ * * [PropertyOptions.ARRAY],
+ * * [PropertyOptions.ARRAY_ORDERED],
+ * * [PropertyOptions.ARRAY_ALTERNATE] or
+ * * [PropertyOptions.ARRAY_ALT_TEXT].
+ *
+ * *Note:* the array options only need to be provided if the array is not
+ * already existing, otherwise you can set them to `null` or use [XMPMeta.appendArrayItem].
+ *
+ * @param itemValue the value of the array item. Has the same usage as propValue in getProperty.
+ * @param itemOptions Option flags describing the item to append ([PropertyOptions])
+ */
+ fun appendArrayItem(
+ schemaNS: String,
+ arrayName: String,
+ arrayOptions: PropertyOptions = PropertyOptions(),
+ itemValue: String,
+ itemOptions: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Provides access to fields within a nested structure. The namespace for the field is passed as
+ * a URI, you need not worry about the path string syntax. The names of fields should be XML
+ * qualified names, that is within an XML namespace. The path syntax for a qualified name uses
+ * the namespace prefix, which is unreliable because the prefix is never guaranteed. The URI is
+ * the formal name, the prefix is just a local shorthand in a given sequence of XML text.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+ * @param structName The name of the struct. May be a general path expression, must not be null
+ * or the empty string. Has the same namespace prefix usage as propName in getProperty.
+ * @param fieldNS The namespace URI for the field. Has the same URI and prefix usage as the
+ * schemaNS parameter.
+ * @param fieldName The name of the field. Must be a single XML name, must not be null or the
+ * empty string. Has the same namespace prefix usage as the structName parameter.
+ * @param fieldValue the value of thefield, if the field has a value.
+ * Has the same usage as propValue in getProperty.
+ * @param options Option flags describing the field. See the earlier description.
+ */
+ fun setStructField(
+ schemaNS: String,
+ structName: String,
+ fieldNS: String,
+ fieldName: String,
+ fieldValue: String?,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Provides access to a qualifier attached to a property. The namespace for the qualifier is
+ * passed as a URI, you need not worry about the path string syntax. In many regards qualifiers
+ * are like struct fields. See the introductory discussion of qualified properties for more
+ * information. The names of qualifiers should be XML qualified names, that is within an XML
+ * namespace. The path syntax for a qualified name uses the namespace prefix, which is
+ * unreliable because the prefix is never guaranteed. The URI is the formal name, the prefix is
+ * just a local shorthand in a given sequence of XML text. The property the qualifier
+ * will be attached has to exist.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in getProperty.
+ * @param propName The name of the property to which the qualifier is attached. Has the same
+ * usage as in getProperty.
+ * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+ * schemaNS parameter.
+ * @param qualName The name of the qualifier. Must be a single XML name, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as the
+ * propName parameter.
+ * @param qualValue A pointer to the `null` terminated UTF-8 string that is the
+ * value of the qualifier, if the qualifier has a value. Has the same usage as propValue
+ * in getProperty.
+ * @param options Option flags describing the qualifier. See the earlier description.
+ */
+ fun setQualifier(
+ schemaNS: String,
+ propName: String,
+ qualNS: String,
+ qualName: String,
+ qualValue: String,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ // ---------------------------------------------------------------------------------------------
+ // Functions for deleting and detecting properties.
+ // These should be obvious from the descriptions of the getters and setters.
+
+ /**
+ * Deletes the given XMP subtree rooted at the given property.
+ * It is not an error if the property does not exist.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property. Has the same usage as in getProperty.
+ */
+ fun deleteProperty(schemaNS: String, propName: String)
+
+ /**
+ * Deletes the given XMP subtree rooted at the given array item.
+ * It is not an error if the array item does not exist.
+ *
+ * @param schemaNS The namespace URI for the array. Has the same usage as in getProperty.
+ * @param arrayName The name of the array. May be a general path expression, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as
+ * propName in `getProperty()`.
+ * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. The
+ * constant `XMPConst.ARRAY_LAST_ITEM` always refers to the last
+ * existing array item.
+ */
+ fun deleteArrayItem(schemaNS: String, arrayName: String, itemIndex: Int)
+
+ /**
+ * Deletes the given XMP subtree rooted at the given struct field.
+ * It is not an error if the field does not exist.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in `getProperty()`.
+ * @param structName The name of the struct. May be a general path expression, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as
+ * propName in getProperty.
+ * @param fieldNS The namespace URI for the field. Has the same URI and prefix usage as the
+ * schemaNS parameter.
+ * @param fieldName The name of the field. Must be a single XML name, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as the
+ * structName parameter.
+ */
+ fun deleteStructField(schemaNS: String, structName: String, fieldNS: String, fieldName: String)
+
+ /**
+ * Deletes the given XMP subtree rooted at the given qualifier.
+ * It is not an error if the qualifier does not exist.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property to which the qualifier is attached. Has the same
+ * usage as in getProperty.
+ * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+ * schemaNS parameter.
+ * @param qualName The name of the qualifier. Must be a single XML name, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as the
+ * propName parameter.
+ */
+ fun deleteQualifier(schemaNS: String, propName: String, qualNS: String, qualName: String)
+
+ /**
+ * Returns whether the property exists.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in getProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @return Returns true if the property exists.
+ */
+ fun doesPropertyExist(schemaNS: String, propName: String): Boolean
+
+ /**
+ * Tells if the array item exists.
+ *
+ * @param schemaNS The namespace URI for the array. Has the same usage as in `getProperty()`.
+ * @param arrayName The name of the array. May be a general path expression, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as
+ * propName in `getProperty()`.
+ * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1. The
+ * constant `XMPConst.ARRAY_LAST_ITEM` always refers to the last
+ * existing array item.
+ * @return Returns `true` if the array exists, `false` otherwise.
+ */
+ fun doesArrayItemExist(schemaNS: String, arrayName: String, itemIndex: Int): Boolean
+
+ /**
+ * Tells if the struct field exists.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in `getProperty()`.
+ * @param structName The name of the struct. May be a general path expression,
+ * must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in `getProperty()`.
+ * @param fieldNS The namespace URI for the field.
+ * Has the same URI and prefix usage as the schemaNS parameter.
+ * @param fieldName The name of the field. Must be a single XML name,
+ * must not be `null` or the empty string.
+ * Has the same namespace prefix usage as the structName parameter.
+ * @return Returns true if the field exists.
+ */
+ fun doesStructFieldExist(
+ schemaNS: String,
+ structName: String,
+ fieldNS: String,
+ fieldName: String
+ ): Boolean
+
+ /**
+ * Tells if the qualifier exists.
+ *
+ * @param schemaNS The namespace URI for the struct. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property to which the qualifier is attached. Has the same
+ * usage as in `getProperty()`.
+ * @param qualNS The namespace URI for the qualifier. Has the same URI and prefix usage as the
+ * schemaNS parameter.
+ * @param qualName The name of the qualifier. Must be a single XML name, must not be
+ * `null` or the empty string. Has the same namespace prefix usage as the
+ * propName parameter.
+ * @return Returns true if the qualifier exists.
+ */
+ fun doesQualifierExist(schemaNS: String, propName: String, qualNS: String, qualName: String): Boolean
+
+ // ---------------------------------------------------------------------------------------------
+ // Specialized Get and Set functions
+
+ /**
+ * These functions provide convenient support for localized text properties, including a number
+ * of special and obscure aspects. Localized text properties are stored in alt-text arrays. They
+ * allow multiple concurrent localizations of a property value, for example a document title or
+ * copyright in several languages. The most important aspect of these functions is that they
+ * select an appropriate array item based on one or two RFC 3066 language tags. One of these
+ * languages, the "specific" language, is preferred and selected if there is an exact match. For
+ * many languages it is also possible to define a "generic" language that may be used if there
+ * is no specific language match. The generic language must be a valid RFC 3066 primary subtag,
+ * or the empty string. For example, a specific language of "en-US" should be used in the US,
+ * and a specific language of "en-UK" should be used in England. It is also appropriate to use
+ * "en" as the generic language in each case. If a US document goes to England, the "en-US"
+ * title is selected by using the "en" generic language and the "en-UK" specific language. It is
+ * considered poor practice, but allowed, to pass a specific language that is just an RFC 3066
+ * primary tag. For example "en" is not a good specific language, it should only be used as a
+ * generic language. Passing "i" or "x" as the generic language is also considered poor practice
+ * but allowed. Advice from the W3C about the use of RFC 3066 language tags can be found at:
+ * http://www.w3.org/International/articles/language-tags/
+ *
+ * *Note:* RFC 3066 language tags must be treated in a case insensitive manner. The XMP
+ * Toolkit does this by normalizing their capitalization:
+ *
+ * * The primary subtag is lower case, the suggested practice of ISO 639.
+ * * All 2 letter secondary subtags are upper case, the suggested practice of ISO 3166.
+ * * All other subtags are lower case. The XMP specification defines an artificial language,
+ * * "x-default", that is used to explicitly denote a default item in an alt-text array.
+ *
+ * The XMP toolkit normalizes alt-text arrays such that the x-default item is the first item.
+ * The SetLocalizedText function has several special features related to the x-default item, see
+ * its description for details. The selection of the array item is the same for GetLocalizedText
+ * and SetLocalizedText:
+ *
+ * * Look for an exact match with the specific language.
+ * * If a generic language is given, look for a partial match.
+ * * Look for an x-default item.
+ * * Choose the first item.
+ *
+ * A partial match with the generic language is where the start of the item's language matches
+ * the generic string and the next character is '-'. An exact match is also recognized as a
+ * degenerate case. It is fine to pass x-default as the specific language. In this case,
+ * selection of an x-default item is an exact match by the first rule, not a selection by the
+ * 3rd rule. The last 2 rules are fallbacks used when the specific and generic languages fail to
+ * produce a match. `getLocalizedText` returns information about a selected item in
+ * an alt-text array. The array item is selected according to the rules given above.
+ *
+ * @param schemaNS The namespace URI for the alt-text array. Has the same usage as in `getProperty()`.
+ * @param altTextName The name of the alt-text array. May be a general path expression, must not
+ * be `null` or the empty string. Has the same namespace prefix usage as
+ * propName in `getProperty()`.
+ * @param genericLang The name of the generic language as an RFC 3066 primary subtag. May be
+ * `null` or the empty string if no generic language is wanted.
+ * @param specificLang The name of the specific language as an RFC 3066 tag. Must not be
+ * `null` or the empty string.
+ * @return Returns an `XMPProperty` containing the value, the actual language and
+ * the options if an appropriate alternate collection item exists, `null`
+ * if the property.
+ * does not exist.
+ *
+ */
+ fun getLocalizedText(
+ schemaNS: String,
+ altTextName: String,
+ genericLang: String?,
+ specificLang: String
+ ): XMPProperty?
+
+ /**
+ * Modifies the value of a selected item in an alt-text array. Creates an appropriate array item
+ * if necessary, and handles special cases for the x-default item. If the selected item is from
+ * a match with the specific language, the value of that item is modified. If the existing value
+ * of that item matches the existing value of the x-default item, the x-default item is also
+ * modified. If the array only has 1 existing item (which is not x-default), an x-default item
+ * is added with the given value. If the selected item is from a match with the generic language
+ * and there are no other generic matches, the value of that item is modified. If the existing
+ * value of that item matches the existing value of the x-default item, the x-default item is
+ * also modified. If the array only has 1 existing item (which is not x-default), an x-default
+ * item is added with the given value. If the selected item is from a partial match with the
+ * generic language and there are other partial matches, a new item is created for the specific
+ * language. The x-default item is not modified. If the selected item is from the last 2 rules
+ * then a new item is created for the specific language. If the array only had an x-default
+ * item, the x-default item is also modified. If the array was empty, items are created for the
+ * specific language and x-default.
+ *
+ * @param schemaNS The namespace URI for the alt-text array. Has the same usage as in `getProperty()`.
+ * @param altTextName The name of the alt-text array. May be a general path expression,
+ * must not be `null` or the empty string.
+ * Has the same namespace prefix usage as propName in `getProperty()`.
+ * @param genericLang The name of the generic language as an RFC 3066 primary subtag.
+ * May be `null` or the empty string if no generic language is wanted.
+ * @param specificLang The name of the specific language as an RFC 3066 tag.
+ * Must not be `null` or the empty string.
+ * @param itemValue A pointer to the `null` terminated UTF-8 string that is the new
+ * value for the appropriate array item.
+ * @param options Option flags, none are defined at present.
+ */
+ fun setLocalizedText(
+ schemaNS: String,
+ altTextName: String,
+ genericLang: String?,
+ specificLang: String,
+ itemValue: String,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ // ---------------------------------------------------------------------------------------------
+ // Functions accessing properties as binary values.
+
+ /**
+ * These are very similar to `getProperty()` and `SetProperty()` above,
+ * but the value is returned or provided in a literal form instead of as a UTF-8 string.
+ * The path composition functions in `XMPPathFactory` may be used to compose an path
+ * expression for fields in nested structures, items in arrays, or qualifiers.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @return Returns a `Boolean` value or `null` if the property does not exist.
+ */
+ fun getPropertyBoolean(schemaNS: String, propName: String): Boolean?
+
+ /**
+ * Convenience method to retrieve the literal value of a property.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @return Returns an `Integer` value or `null` if the property does not exist.
+ */
+ fun getPropertyInteger(schemaNS: String, propName: String): Int?
+
+ /**
+ * Convenience method to retrieve the literal value of a property.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @return Returns a `Long` value or `null` if the property does not exist.
+ */
+ fun getPropertyLong(schemaNS: String, propName: String): Long?
+
+ /**
+ * Convenience method to retrieve the literal value of a property.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @return Returns a `Double` value or `null` if the property does not exist.
+ */
+ fun getPropertyDouble(schemaNS: String, propName: String): Double?
+
+ /**
+ * Convenience method to retrieve the literal value of a property.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @return Returns a `byte[]`-array contained the decoded base64 value or `null` if the property does
+ * not exist.
+ */
+ fun getPropertyBase64(schemaNS: String, propName: String): ByteArray?
+
+ /**
+ * Convenience method to retrieve the literal value of a property.
+ *
+ * *Note:* There is no `setPropertyString()`,
+ * because `setProperty()` sets a string value.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `getProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @return Returns a `String` value or `null` if the property does not exist.
+ */
+ fun getPropertyString(schemaNS: String, propName: String): String?
+
+ /**
+ * Convenience method to set a property to a literal `boolean` value.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `setProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @param propValue the literal property value as `boolean`.
+ * @param options options of the property to set (optional).
+ */
+ fun setPropertyBoolean(
+ schemaNS: String,
+ propName: String,
+ propValue: Boolean,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Convenience method to set a property to a literal `int` value.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `setProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @param propValue the literal property value as `int`.
+ * @param options options of the property to set (optional).
+ *
+ */
+ fun setPropertyInteger(
+ schemaNS: String,
+ propName: String,
+ propValue: Int,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Convenience method to set a property to a literal `long` value.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `setProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @param propValue the literal property value as `long`.
+ * @param options options of the property to set (optional).
+ */
+ fun setPropertyLong(
+ schemaNS: String,
+ propName: String,
+ propValue: Long,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Convenience method to set a property to a literal `double` value.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `setProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @param propValue the literal property value as `double`.
+ * @param options options of the property to set (optional).
+ */
+ fun setPropertyDouble(
+ schemaNS: String,
+ propName: String,
+ propValue: Double,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Convenience method to set a property from a binary `byte[]`-array,
+ * which is serialized as base64-string.
+ *
+ * @param schemaNS The namespace URI for the property. Has the same usage as in `setProperty()`.
+ * @param propName The name of the property. Has the same usage as in `getProperty()`.
+ * @param propValue the literal property value as byte array.
+ * @param options options of the property to set (optional).
+ */
+ fun setPropertyBase64(
+ schemaNS: String,
+ propName: String,
+ propValue: ByteArray,
+ options: PropertyOptions = PropertyOptions()
+ )
+
+ /**
+ * Constructs an iterator for the properties within this XMP object.
+ *
+ * @return Returns an `XMPIterator`.
+ */
+ fun iterator(): XMPIterator
+
+ /**
+ * Constructs an iterator for the properties within this XMP object using some options.
+ *
+ * @param options Option flags to control the iteration.
+ * @return Returns an `XMPIterator`.
+ */
+ fun iterator(options: IteratorOptions): XMPIterator
+
+ /**
+ * Construct an iterator for the properties within an XMP object. According to the parameters it iterates
+ * the entire data tree, properties within a specific schema, or a subtree rooted at a specific node.
+ *
+ * @param schemaNS Optional schema namespace URI to restrict the iteration.
+ * Omitted (visit all schema) by passing `null` or empty String.
+ * @param propName Optional property name to restrict the iteration. May be an arbitrary path
+ * expression. Omitted (visit all properties) by passing `null` or empty
+ * String. If no schema URI is given, it is ignored.
+ * @param options Option flags to control the iteration. See [IteratorOptions] for details.
+ * @return Returns an `XMPIterator` for this `XMPMeta`-object considering the given options.
+ */
+ fun iterator(
+ schemaNS: String?,
+ propName: String?,
+ options: IteratorOptions = IteratorOptions()
+ ): XMPIterator
+
+ /**
+ * This correlates to the about-attribute,
+ * returns the empty String if no name is set.
+ *
+ * @return Returns the name of the XMP object.
+ */
+ fun getObjectName(): String
+
+ /**
+ * @param name Sets the name of the XMP object.
+ */
+ fun setObjectName(name: String)
+
+ /**
+ * @return Returns the unparsed content of the <?xpacket> processing instruction.
+ * This contains normally the attribute-like elements 'begin="<BOM>"
+ * id="W5M0MpCehiHzreSzNTczkc9d"' and possibly the deprecated elements 'bytes="1234"' or
+ * 'encoding="XXX"'. If the parsed packet has not been wrapped into an xpacket,
+ * `null` is returned.
+ */
+ fun getPacketHeader(): String?
+
+ /**
+ * Sorts the complete datamodel according to the following rules:
+ *
+ * * Schema nodes are sorted by prefix.
+ * * Properties at top level and within structs are sorted by full name, that is prefix + local name.
+ * * Array items are not sorted, even if they have no certain order such as bags.
+ * * Qualifier are sorted, with the exception of "xml:lang" and/or "rdf:type"
+ * that stay at the top of the list in that order.
+ */
+ fun sort()
+
+ /**
+ * Perform the normalization as a separate parsing step.
+ * Normally it is done during parsing, unless the parsing option
+ * [ParseOptions.OMIT_NORMALIZATION] is set to `true`.
+ * *Note:* It does no harm to call this method to an already normalized xmp object.
+ * It was a PDF/A requirement to get hand on the unnormalized `XMPMeta` object.
+ */
+ fun normalize(options: ParseOptions = ParseOptions())
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPMetaFactory.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPMetaFactory.kt
new file mode 100644
index 0000000..690589f
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPMetaFactory.kt
@@ -0,0 +1,50 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+import com.ashampoo.xmp.impl.XMPMetaImpl
+import com.ashampoo.xmp.impl.XMPMetaParser
+import com.ashampoo.xmp.impl.XMPRDFWriter
+import com.ashampoo.xmp.impl.XMPSchemaRegistryImpl
+import com.ashampoo.xmp.options.ParseOptions
+import com.ashampoo.xmp.options.SerializeOptions
+
+/**
+ * Creates `XMPMeta`-instances from an `InputStream`
+ */
+object XMPMetaFactory {
+
+ @kotlin.jvm.JvmStatic
+ val schemaRegistry = XMPSchemaRegistryImpl
+
+ @kotlin.jvm.JvmStatic
+ val versionInfo = XMPVersionInfo
+
+ fun create(): XMPMeta = XMPMetaImpl()
+
+ fun parseFromString(
+ packet: String,
+ options: ParseOptions = ParseOptions()
+ ): XMPMeta =
+ XMPMetaParser.parse(packet, options)
+
+ fun serializeToString(
+ xmp: XMPMeta,
+ options: SerializeOptions = SerializeOptions()
+ ): String {
+
+ require(xmp is XMPMetaImpl) { "Serialization only works with XMPMetaImpl" }
+
+ /* sort the internal data model on demand */
+ if (options.getSort())
+ xmp.sort()
+
+ return XMPRDFWriter(xmp, options).serialize()
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPPathFactory.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPPathFactory.kt
new file mode 100644
index 0000000..8bf7ec6
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPPathFactory.kt
@@ -0,0 +1,179 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+import com.ashampoo.xmp.impl.Utils
+import com.ashampoo.xmp.impl.xpath.XMPPath
+import com.ashampoo.xmp.impl.xpath.XMPPathParser
+
+/**
+ * Utility services for the metadata object. It has only public static functions, you cannot create
+ * an object. These are all functions that layer cleanly on top of the core XMP toolkit.
+ *
+ * These functions provide support for composing path expressions to deeply nested properties. The
+ * functions `XMPMeta` such as `getProperty()`,
+ * `getArrayItem()` and `getStructField()` provide easy access to top
+ * level simple properties, items in top level arrays, and fields of top level structs. They do not
+ * provide convenient access to more complex things like fields several levels deep in a complex
+ * struct, or fields within an array of structs, or items of an array that is a field of a struct.
+ * These functions can also be used to compose paths to top level array items or struct fields so
+ * that you can use the binary accessors like `getPropertyAsInteger()`.
+ *
+ * You can use these functions is to compose a complete path expression, or all but the last
+ * component. Suppose you have a property that is an array of integers within a struct.
+ *
+ * *Note:* It might look confusing that the schemaNS is passed in all of the calls above.
+ * This is because the XMP toolkit keeps the top level "schema" namespace separate from
+ * the rest of the path expression.
+ *
+ * *Note:* These methods are much simpler than in the C++-API, they don't check the given path or array indices.
+ */
+object XMPPathFactory {
+
+ /**
+ * Compose the path expression for an item in an array.
+ *
+ * @param arrayName The name of the array.
+ * May be a general path expression, must not be `null` or the empty string.
+ * @param itemIndex The index of the desired item. Arrays in XMP are indexed from 1.
+ * 0 and below means last array item and renders as `[last()]`.
+ * @return Returns the composed path basing on fullPath. This will be of the form
+ * ns:arrayName[i], where "ns" is the prefix for schemaNS and
+ * "i" is the decimal representation of itemIndex.
+ */
+ @kotlin.jvm.JvmStatic
+ fun composeArrayItemPath(arrayName: String, itemIndex: Int): String {
+
+ if (itemIndex > 0)
+ return "$arrayName[$itemIndex]"
+
+ if (itemIndex == XMPConst.ARRAY_LAST_ITEM)
+ return "$arrayName[last()]"
+
+ throw XMPException("Array index must be larger than zero", XMPError.BADINDEX)
+ }
+
+ /**
+ * Compose the path expression for a field in a struct. The result can be added to the
+ * path of
+ *
+ * @param fieldNS The namespace URI for the field. Must not be `null` or the empty string.
+ * @param fieldName The name of the field. Must be a simple XML name,
+ * must not be `null` or the empty string.
+ * @return Returns the composed path. This will be of the form
+ * ns:structName/fNS:fieldName, where "ns" is the prefix for
+ * schemaNS and "fNS" is the prefix for fieldNS.
+ */
+ @kotlin.jvm.JvmStatic
+ fun composeStructFieldPath(fieldNS: String, fieldName: String): String {
+
+ if (fieldNS.length == 0)
+ throw XMPException("Empty field namespace URI", XMPError.BADSCHEMA)
+
+ if (fieldName.length == 0)
+ throw XMPException("Empty field name", XMPError.BADXPATH)
+
+ val fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName)
+
+ if (fieldPath.size() != 2)
+ throw XMPException("The field name must be simple", XMPError.BADXPATH)
+
+ return '/'.toString() + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).name
+ }
+
+ /**
+ * Compose the path expression for a qualifier.
+ *
+ * @param qualNS The namespace URI for the qualifier.
+ * May be `null` or the empty string if the qualifier is in the XML empty namespace.
+ * @param qualName The name of the qualifier.
+ * Must be a simple XML name, must not be `null` or the empty string.
+ * @return Returns the composed path. This will be of the form
+ * ns:propName/?qNS:qualName, where "ns" is the prefix for
+ * schemaNS and "qNS" is the prefix for qualNS.
+ */
+ @kotlin.jvm.JvmStatic
+ fun composeQualifierPath(qualNS: String, qualName: String): String {
+
+ if (qualNS.length == 0)
+ throw XMPException("Empty qualifier namespace URI", XMPError.BADSCHEMA)
+
+ if (qualName.length == 0)
+ throw XMPException("Empty qualifier name", XMPError.BADXPATH)
+
+ val qualPath = XMPPathParser.expandXPath(qualNS, qualName)
+
+ if (qualPath.size() != 2)
+ throw XMPException("The qualifier name must be simple", XMPError.BADXPATH)
+
+ return "/?" + qualPath.getSegment(XMPPath.STEP_ROOT_PROP).name
+ }
+
+ /**
+ * Compose the path expression to select an alternate item by language. The
+ * path syntax allows two forms of "content addressing" that may
+ * be used to select an item in an array of alternatives. The form used in
+ * ComposeLangSelector lets you select an item in an alt-text array based on
+ * the value of its xml:lang qualifier. The other form of content
+ * addressing is shown in ComposeFieldSelector.
+ *
+ * ComposeLangSelector does not supplant SetLocalizedText or GetLocalizedText.
+ * They should generally be used, as they provide extra logic to choose the appropriate
+ * language and maintain consistency with the 'x-default' value.
+ * ComposeLangSelector gives you an path expression that is explicitly and
+ * only for the language given in the langName parameter.
+ *
+ * @param arrayName The name of the array.
+ * May be a general path expression, must not be `null` or the empty string.
+ * @param langName The RFC 3066 code for the desired language.
+ * @return Returns the composed path. This will be of the form
+ * ns:arrayName[@xml:lang='langName'], where
+ * "ns" is the prefix for schemaNS.
+ */
+ fun composeLangSelector(arrayName: String, langName: String): String =
+ arrayName + "[?xml:lang=\"" + Utils.normalizeLangValue(langName) + "\"]"
+
+ /**
+ * Compose the path expression to select an alternate item by a field's value. The path syntax
+ * allows two forms of "content addressing" that may be used to select an item in an
+ * array of alternatives. The form used in ComposeFieldSelector lets you select an item in an
+ * array of structs based on the value of one of the fields in the structs. The other form of
+ * content addressing is shown in ComposeLangSelector. For example, consider a simple struct
+ * that has two fields, the name of a city and the URI of an FTP site in that city. Use this to
+ * create an array of download alternatives. You can show the user a popup built from the values
+ * of the city fields.
+ *
+ * @param arrayName The name of the array.
+ * May be a general path expression, must not be `null` or the empty string.
+ * @param fieldNS The namespace URI for the field used as the selector.
+ * Must not be `null` or the empty string.
+ * @param fieldName The name of the field used as the selector.
+ * Must be a simple XML name, must not be `null` or the empty string.
+ * It must be the name of a field that is itself simple.
+ * @param fieldValue The desired value of the field.
+ * @return Returns the composed path. This will be of the form
+ * ns:arrayName[fNS:fieldName='fieldValue'], where "ns" is the
+ * prefix for schemaNS and "fNS" is the prefix for fieldNS.
+ */
+ fun composeFieldSelector(
+ arrayName: String,
+ fieldNS: String?,
+ fieldName: String?,
+ fieldValue: String
+ ): String {
+
+ val fieldPath = XMPPathParser.expandXPath(fieldNS, fieldName)
+
+ if (fieldPath.size() != 2)
+ throw XMPException("The fieldName name must be simple", XMPError.BADXPATH)
+
+ return arrayName +
+ '[' + fieldPath.getSegment(XMPPath.STEP_ROOT_PROP).name + "=\"" + fieldValue + "\"]"
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPSchemaRegistry.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPSchemaRegistry.kt
new file mode 100644
index 0000000..26e1ff0
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPSchemaRegistry.kt
@@ -0,0 +1,142 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+import com.ashampoo.xmp.properties.XMPAliasInfo
+
+/**
+ * The schema registry keeps track of all namespaces and aliases used in the XMP
+ * metadata. At initialisation time, the default namespaces and default aliases
+ * are automatically registered. **Namespaces** must be registered before
+ * used in namespace URI parameters or path expressions. Within the XMP Toolkit
+ * the registered namespace URIs and prefixes must be unique. Additional
+ * namespaces encountered when parsing RDF are automatically registered. The
+ * namespace URI should always end in an XML name separator such as '/' or '#'.
+ * This is because some forms of RDF shorthand catenate a namespace URI with an
+ * element name to form a new URI.
+ *
+ * **Aliases** in XMP serve the same purpose as Windows file shortcuts,
+ * Macintosh file aliases, or UNIX file symbolic links. The aliases are simply
+ * multiple names for the same property. One distinction of XMP aliases is that
+ * they are ordered, there is an alias name pointing to an actual name. The
+ * primary significance of the actual name is that it is the preferred name for
+ * output, generally the most widely recognized name.
+ *
+ * The names that can be aliased in XMP are restricted. The alias must be a top
+ * level property name, not a field within a structure or an element within an
+ * array. The actual may be a top level property name, the first element within
+ * a top level array, or the default element in an alt-text array. This does not
+ * mean the alias can only be a simple property. It is OK to alias a top level
+ * structure or array to an identical top level structure or array, or to the
+ * first item of an array of structures.
+ */
+interface XMPSchemaRegistry {
+
+ // ---------------------------------------------------------------------------------------------
+ // Namespace Functions
+
+ /**
+ * Register a namespace URI with a suggested prefix. It is not an error if
+ * the URI is already registered, no matter what the prefix is. If the URI
+ * is not registered but the suggested prefix is in use, a unique prefix is
+ * created from the suggested one. The actual registeed prefix is always
+ * returned. The function result tells if the registered prefix is the
+ * suggested one.
+ *
+ * Note: No checking is presently done on either the URI or the prefix.
+ *
+ * @param namespaceURI The URI for the namespace. Must be a valid XML URI.
+ * @param suggestedPrefix The suggested prefix to be used if the URI is not yet registered.
+ * Must be a valid XML name.
+ * @return Returns the registered prefix for this URI, is equal to the suggestedPrefix if the
+ * namespace hasn't been registered before, otherwise the existing prefix.
+ */
+ fun registerNamespace(namespaceURI: String, suggestedPrefix: String): String
+
+ /**
+ * Obtain the prefix for a registered namespace URI.
+ *
+ * It is not an error if the namespace URI is not registered.
+ *
+ * @param namespaceURI The URI for the namespace. Must not be null or the empty string.
+ * @return Returns the prefix registered for this namespace URI or null.
+ */
+ fun getNamespacePrefix(namespaceURI: String): String?
+
+ /**
+ * Obtain the URI for a registered namespace prefix.
+ *
+ * It is not an error if the namespace prefix is not registered.
+ *
+ * @param namespacePrefix The prefix for the namespace. Must not be null or the empty string.
+ * @return Returns the URI registered for this prefix or null.
+ */
+ fun getNamespaceURI(namespacePrefix: String): String?
+
+ /**
+ * @return Returns the registered prefix/namespace-pairs as map, where the keys are the
+ * namespaces and the values are the prefixes.
+ */
+ fun getNamespaces(): Map
+
+ /**
+ * @return Returns the registered namespace/prefix-pairs as map, where the keys are the
+ * prefixes and the values are the namespaces.
+ */
+ fun getPrefixes(): Map
+
+ /**
+ * Deletes a namespace from the registry.
+ *
+ * Does nothing if the URI is not registered, or if the namespaceURI
+ * parameter is null or the empty string.
+ *
+ * @param namespaceURI The URI for the namespace.
+ */
+ fun deleteNamespace(namespaceURI: String)
+
+ // ---------------------------------------------------------------------------------------------
+ // Alias Functions
+
+ /**
+ * Determines if a name is an alias, and what it is aliased to.
+ *
+ * @param aliasNS The namespace URI of the alias. Must not be `null` or the empty string.
+ * @param aliasProp The name of the alias.
+ * May be an arbitrary path expression path, must not be `null` or the empty string.
+ * @return Returns the `XMPAliasInfo` for the given alias namespace and property
+ * or `null` if there is no such alias.
+ */
+ fun resolveAlias(aliasNS: String, aliasProp: String): XMPAliasInfo?
+
+ /**
+ * Collects all aliases that are contained in the provided namespace.
+ * If nothing is found, an empty array is returned.
+ *
+ * @param aliasNS a schema namespace URI
+ * @return Returns all alias infos from aliases that are contained in the provided namespace.
+ */
+ fun findAliases(aliasNS: String): Set
+
+ /**
+ * Searches for registered aliases.
+ *
+ * @param qname an XML conform qname
+ * @return Returns if an alias definition for the given qname to another
+ * schema and property is registered.
+ */
+ fun findAlias(qname: String): XMPAliasInfo?
+
+ /**
+ * @return Returns the registered aliases as map, where the key is the "qname" (prefix and name)
+ * and the value an `XMPAliasInfo`-object.
+ */
+ fun getAliases(): Map
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPUtils.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPUtils.kt
new file mode 100644
index 0000000..adde755
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPUtils.kt
@@ -0,0 +1,120 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp
+
+import kotlin.io.encoding.Base64
+import kotlin.io.encoding.ExperimentalEncodingApi
+
+/**
+ * Utility methods for XMP. I included only those that are different from the
+ * Java default conversion utilities.
+ */
+object XMPUtils {
+
+ /**
+ * Convert from string to Boolean.
+ *
+ * @param value The string representation of the Boolean.
+ * @return The appropriate boolean value for the string.
+ * The checked values for `true` and `false` are:
+ * * [XMPConst.TRUESTR] and [XMPConst.FALSESTR]
+ * * "t" and "f"
+ * * "on" and "off"
+ * * "yes" and "no"
+ * * "value != 0" and "value == 0"
+ */
+ @kotlin.jvm.JvmStatic
+ fun convertToBoolean(value: String?): Boolean {
+
+ if (value == null || value.length == 0)
+ throw XMPException("Empty convert-string", XMPError.BADVALUE)
+
+ val valueLowercase = value.lowercase()
+
+ try {
+
+ /* First try interpretation as Integer (anything not 0 is true) */
+ return valueLowercase.toInt() != 0
+
+ } catch (ex: NumberFormatException) {
+
+ /* Fallback to other common true values */
+ return "true" == valueLowercase || "t" == valueLowercase ||
+ "on" == valueLowercase || "yes" == valueLowercase
+ }
+ }
+
+ @kotlin.jvm.JvmStatic
+ fun convertToInteger(rawValue: String?): Int {
+ try {
+
+ if (rawValue == null || rawValue.length == 0)
+ throw XMPException("Empty convert-string", XMPError.BADVALUE)
+
+ return if (rawValue.startsWith("0x"))
+ rawValue.substring(2).toInt(16)
+ else
+ rawValue.toInt()
+
+ } catch (ex: NumberFormatException) {
+ throw XMPException("Invalid integer string", XMPError.BADVALUE, ex)
+ }
+ }
+
+ @kotlin.jvm.JvmStatic
+ fun convertToLong(rawValue: String?): Long {
+
+ try {
+
+ if (rawValue == null || rawValue.length == 0)
+ throw XMPException("Empty convert-string", XMPError.BADVALUE)
+
+ return if (rawValue.startsWith("0x"))
+ rawValue.substring(2).toLong(16)
+ else
+ rawValue.toLong()
+
+ } catch (ex: NumberFormatException) {
+ throw XMPException("Invalid long string", XMPError.BADVALUE, ex)
+ }
+ }
+
+ @kotlin.jvm.JvmStatic
+ fun convertToDouble(rawValue: String?): Double {
+
+ try {
+
+ if (rawValue == null || rawValue.length == 0)
+ throw XMPException("Empty convert-string", XMPError.BADVALUE)
+
+ return rawValue.toDouble()
+
+ } catch (ex: NumberFormatException) {
+ throw XMPException("Invalid double string", XMPError.BADVALUE, ex)
+ }
+ }
+
+ @OptIn(ExperimentalEncodingApi::class)
+ @kotlin.jvm.JvmStatic
+ fun encodeBase64(buffer: ByteArray): String =
+ Base64.encode(buffer)
+
+ @OptIn(ExperimentalEncodingApi::class)
+ @kotlin.jvm.JvmStatic
+ fun decodeBase64(base64String: String): ByteArray {
+
+ try {
+
+ return Base64.decode(base64String.encodeToByteArray())
+
+ } catch (ex: Throwable) {
+ throw XMPException("Invalid base64 string", XMPError.BADVALUE, ex)
+ }
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/XMPVersionInfo.kt b/src/commonMain/kotlin/com/ashampoo/xmp/XMPVersionInfo.kt
new file mode 100644
index 0000000..d5fc837
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/XMPVersionInfo.kt
@@ -0,0 +1,21 @@
+package com.ashampoo.xmp
+
+/**
+ * We ported from version 1.5.3, which was the final release available on
+ * https://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html
+ * under the BSD license, prior to the webpage being taken down.
+ * Hence we report this as the used version.
+ */
+@Suppress("MagicNumber")
+object XMPVersionInfo {
+
+ const val VERSION_MESSAGE = "Adobe XMP Core 5.1.3"
+
+ const val major: Int = 5
+ const val minor: Int = 1
+ const val micro: Int = 3
+ const val build: Int = 0
+ const val isDebug: Boolean = false
+ const val message: String = VERSION_MESSAGE
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/DomParser.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/DomParser.kt
new file mode 100644
index 0000000..e512338
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/DomParser.kt
@@ -0,0 +1,9 @@
+package com.ashampoo.xmp.impl
+
+import nl.adaptivity.xmlutil.dom.Document
+
+fun interface DomParser {
+
+ fun parseDocumentFromString(input: String): Document
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/QName.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/QName.kt
new file mode 100644
index 0000000..0595009
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/QName.kt
@@ -0,0 +1,51 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+class QName {
+
+ /**
+ * XML namespace prefix
+ */
+ var prefix: String? = null
+ private set
+
+ /**
+ * XML localname
+ */
+ var localName: String? = null
+ private set
+
+ /**
+ * Splits a qname into prefix and localname.
+ *
+ * @param qname a QName
+ */
+ constructor(qname: String) {
+
+ val colon = qname.indexOf(':')
+
+ if (colon >= 0) {
+ prefix = qname.substring(0, colon)
+ localName = qname.substring(colon + 1)
+ } else {
+ prefix = ""
+ localName = qname
+ }
+ }
+
+ constructor(prefix: String?, localName: String?) {
+ this.prefix = prefix
+ this.localName = localName
+ }
+
+ fun hasPrefix(): Boolean =
+ prefix != null && prefix!!.length > 0
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/Utils.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/Utils.kt
new file mode 100644
index 0000000..420595d
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/Utils.kt
@@ -0,0 +1,367 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+
+/**
+ * Utility functions for the XMPToolkit implementation.
+ */
+object Utils {
+
+ /**
+ * segments of a UUID
+ */
+ const val UUID_SEGMENT_COUNT = 4
+
+ /**
+ * length of a UUID
+ */
+ const val UUID_LENGTH = 32 + UUID_SEGMENT_COUNT
+
+ /**
+ * table of XML name start chars (<= 0xFF)
+ */
+ private val xmlNameStartChars = BooleanArray(0x0100)
+
+ /**
+ * table of XML name chars (<= 0xFF)
+ */
+ private val xmlNameChars = BooleanArray(0x0100)
+
+ private val controlCharRegex = Regex("[\\p{Cntrl}]")
+
+ /** init char tables */
+ init {
+ initCharTables()
+ }
+
+ /**
+ * Normalize an xml:lang value so that comparisons are effectively case
+ * insensitive as required by RFC 3066 (which superceeds RFC 1766). The
+ * normalization rules:
+ *
+ * * The primary subtag is lower case, the suggested practice of ISO 639.
+ * * All 2 letter secondary subtags are upper case, the suggested
+ * practice of ISO 3166.
+ * * All other subtags are lower case.
+ *
+ *
+ * @param value raw value
+ * @return Returns the normalized value.
+ */
+ @kotlin.jvm.JvmStatic
+ fun normalizeLangValue(value: String): String {
+
+ // don't normalize x-default
+ if (XMPConst.X_DEFAULT == value)
+ return value
+
+ var subTag = 1
+ val buffer = StringBuilder()
+
+ for (i in 0 until value.length) {
+
+ when (value[i]) {
+
+ '-', '_' -> {
+ /* Move to next subtag and convert underscore to hyphen */
+ buffer.append('-')
+ subTag++
+ }
+
+ ' ' -> {
+ /* Leave as is. */
+ }
+
+ else ->
+ /* Convert second subtag to uppercase, all other to lowercase */
+ if (subTag != 2)
+ buffer.append(value[i].lowercaseChar())
+ else
+ buffer.append(value[i].uppercaseChar())
+ }
+ }
+
+ return buffer.toString()
+ }
+
+ /**
+ * Split the name and value parts for field and qualifier selectors:
+ *
+ * * [qualName="value"] - An element in an array of structs, chosen by a field value.
+ * * [?qualName="value"] - An element in an array, chosen by a qualifier value.
+ *
+ * The value portion is a string quoted by ''' or '"'. The value may contain
+ * any character including a doubled quoting character. The value may be
+ * empty. *Note:* It is assumed that the expression is formal
+ * correct
+ *
+ * @param selector the selector
+ * @return Returns an array where the first entry contains the name and the second the value.
+ */
+ @kotlin.jvm.JvmStatic
+ fun splitNameAndValue(selector: String): Array {
+
+ // get the name
+ val eq = selector.indexOf('=')
+
+ var pos = 1
+
+ if (selector[pos] == '?')
+ pos++
+
+ val name = selector.substring(pos, eq)
+
+ // get the value
+ pos = eq + 1
+
+ val quote = selector[pos]
+
+ pos++
+
+ val end = selector.length - 2 // quote and ]
+
+ val value = StringBuilder(end - eq)
+
+ while (pos < end) {
+
+ value.append(selector[pos])
+
+ pos++
+
+ if (selector[pos] == quote) {
+ // skip one quote in value
+ pos++
+ }
+ }
+
+ return arrayOf(name, value.toString())
+ }
+
+ /**
+ * Check some requirements for an UUID:
+ *
+ * * Length of the UUID is 32
+ * * The Delimiter count is 4 and all the 4 delimiter are on their right position (8,13,18,23)
+ *
+ * @param uuid uuid to test
+ * @return true - this is a well formed UUID, false - UUID has not the expected format
+ */
+ @kotlin.jvm.JvmStatic
+ fun checkUUIDFormat(uuid: String?): Boolean {
+
+ var result = true
+ var delimCnt = 0
+ var delimPos = 0
+
+ if (uuid == null)
+ return false
+
+ delimPos = 0
+
+ while (delimPos < uuid.length) {
+
+ if (uuid[delimPos] == '-') {
+ delimCnt++
+ result = result && (delimPos == 8 || delimPos == 13 || delimPos == 18 || delimPos == 23)
+ }
+
+ delimPos++
+ }
+
+ return result && UUID_SEGMENT_COUNT == delimCnt && UUID_LENGTH == delimPos
+ }
+
+ /**
+ * Simple check for valid XMLNames. Within ASCII range
+ * ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]
+ * are accepted, above all characters (which is not entirely
+ * correct according to the XML Spec.
+ *
+ * @param name an XML Name
+ * @return Return `true` if the name is correct.
+ */
+ fun isXMLName(name: String): Boolean {
+
+ if (name.length > 0 && !isNameStartChar(name[0]))
+ return false
+
+ for (i in 1 until name.length)
+ if (!isNameChar(name[i]))
+ return false
+
+ return true
+ }
+
+ /**
+ * Checks if the value is a legal "unqualified" XML name, as
+ * defined in the XML Namespaces proposed recommendation.
+ * These are XML names, except that they must not contain a colon.
+ *
+ * @param name the value to check
+ * @return Returns true if the name is a valid "unqualified" XML name.
+ */
+ @kotlin.jvm.JvmStatic
+ fun isXMLNameNS(name: String): Boolean {
+
+ if (name.length > 0 && (!isNameStartChar(name[0]) || name[0] == ':'))
+ return false
+
+ for (index in 1 until name.length)
+ if (!isNameChar(name[index]) || name[index] == ':')
+ return false
+
+ return true
+ }
+
+ /**
+ * Serializes the node value in XML encoding. Its used for tag bodies and attributes.
+ *
+ * *Note:* The attribute is always limited by quotes, thats why `'` is never serialized.
+ *
+ * *Note:* Control chars are written unescaped, but if the user uses others than tab, LF
+ * and CR the resulting XML will become invalid.
+ *
+ * @param value a string
+ * @param forAttribute flag if string is attribute value (need to additional escape quotes)
+ * @param escapeWhitespaces Decides if LF, CR and TAB are escaped.
+ * @return Returns the value ready for XML output.
+ */
+ @kotlin.jvm.JvmStatic
+ fun escapeXML(value: String, forAttribute: Boolean, escapeWhitespaces: Boolean): String {
+
+ // quick check if character are contained that need special treatment
+ var needsEscaping = false
+
+ for (index in 0 until value.length) {
+
+ val c = value[index]
+
+ if (
+ c == '<' || c == '>' || c == '&' || escapeWhitespaces &&
+ (c == '\t' || c == '\n' || c == '\r') || forAttribute && c == '"'
+ ) {
+ needsEscaping = true
+ break
+ }
+ }
+
+ if (!needsEscaping)
+ return value
+
+ // slow path with escaping
+ val buffer = StringBuilder(value.length * 4 / 3)
+
+ for (index in 0 until value.length) {
+
+ val char = value[index]
+
+ if (!(escapeWhitespaces && (char == '\t' || char == '\n' || char == '\r'))) {
+
+ when (char) {
+
+ '<' -> {
+ buffer.append("<")
+ continue
+ }
+
+ '>' -> {
+ buffer.append(">")
+ continue
+ }
+
+ '&' -> {
+ buffer.append("&")
+ continue
+ }
+
+ '"' -> {
+ buffer.append(if (forAttribute) """ else "\"")
+ continue
+ }
+
+ else -> {
+ buffer.append(char)
+ continue
+ }
+ }
+
+ } else {
+
+ // write control chars escaped,
+ // if there are others than tab, LF and CR the xml will become invalid.
+ buffer.append("")
+ buffer.append(char.code.toString(16).uppercase())
+ buffer.append(';')
+ }
+ }
+
+ return buffer.toString()
+ }
+
+ /**
+ * Replaces the ASCII control chars with a space.
+ *
+ * @param value a node value
+ * @return Returns the cleaned up value
+ */
+ @kotlin.jvm.JvmStatic
+ fun replaceControlCharsWithSpace(value: String): String =
+ value.replace(controlCharRegex, " ")
+
+ /**
+ * Simple check if a character is a valid XML start name char.
+ * All characters according to the XML Spec 1.1 are accepted:
+ * http://www.w3.org/TR/xml11/#NT-NameStartChar
+ *
+ * @param ch a character
+ * @return Returns true if the character is a valid first char of an XML name.
+ */
+ private fun isNameStartChar(ch: Char): Boolean =
+ ch.code <= 0xFF && xmlNameStartChars[ch.code] || ch.code >= 0x100 && ch.code <= 0x2FF ||
+ ch.code >= 0x370 && ch.code <= 0x37D || ch.code >= 0x37F && ch.code <= 0x1FFF ||
+ ch.code >= 0x200C && ch.code <= 0x200D || ch.code >= 0x2070 && ch.code <= 0x218F ||
+ ch.code >= 0x2C00 && ch.code <= 0x2FEF || ch.code >= 0x3001 && ch.code <= 0xD7FF ||
+ ch.code >= 0xF900 && ch.code <= 0xFDCF || ch.code >= 0xFDF0 && ch.code <= 0xFFFD ||
+ ch.code >= 0x10000 && ch.code <= 0xEFFFF
+
+ /**
+ * Simple check if a character is a valid XML name char
+ * (every char except the first one), according to the XML Spec 1.1:
+ * http://www.w3.org/TR/xml11/#NT-NameChar
+ *
+ * @param ch a character
+ * @return Returns true if the character is a valid char of an XML name.
+ */
+ private fun isNameChar(ch: Char): Boolean =
+ ch.code <= 0xFF && xmlNameChars[ch.code] || isNameStartChar(ch) ||
+ ch.code >= 0x300 && ch.code <= 0x36F || ch.code >= 0x203F && ch.code <= 0x2040
+
+ /**
+ * Initializes the char tables for the chars 0x00-0xFF for later use,
+ * according to the XML 1.1 specification at http://www.w3.org/TR/xml11
+ */
+ private fun initCharTables() {
+
+ var ch = 0.toChar()
+
+ while (ch < xmlNameChars.size.toChar()) {
+
+ xmlNameStartChars[ch.code] = ch == ':' || 'A' <= ch && ch <= 'Z' || ch == '_' ||
+ 'a' <= ch && ch <= 'z' || 0xC0 <= ch.code && ch.code <= 0xD6 ||
+ 0xD8 <= ch.code && ch.code <= 0xF6 || 0xF8 <= ch.code && ch.code <= 0xFF
+
+ xmlNameChars[ch.code] = xmlNameStartChars[ch.code] || ch == '-' || ch == '.' ||
+ '0' <= ch && ch <= '9' || ch.code == 0xB7
+
+ ch++
+ }
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPIteratorImpl.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPIteratorImpl.kt
new file mode 100644
index 0000000..9c4f494
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPIteratorImpl.kt
@@ -0,0 +1,498 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPIterator
+import com.ashampoo.xmp.XMPMetaFactory.schemaRegistry
+import com.ashampoo.xmp.impl.XMPNodeUtils.findNode
+import com.ashampoo.xmp.impl.XMPNodeUtils.findSchemaNode
+import com.ashampoo.xmp.impl.xpath.XMPPath
+import com.ashampoo.xmp.impl.xpath.XMPPathParser.expandXPath
+import com.ashampoo.xmp.options.IteratorOptions
+import com.ashampoo.xmp.options.PropertyOptions
+import com.ashampoo.xmp.properties.XMPPropertyInfo
+
+/**
+ * The `XMPIterator` implementation.
+ * Iterates the XMP Tree according to a set of options.
+ * During the iteration the XMPMeta-object must not be changed.
+ * Calls to `skipSubtree()` / `skipSiblings()` will affect the iteration.
+ */
+class XMPIteratorImpl(
+ xmp: XMPMetaImpl,
+ schemaNS: String?,
+ propPath: String?,
+ options: IteratorOptions?
+) : XMPIterator {
+
+ private val options: IteratorOptions
+
+ /**
+ * the base namespace of the property path, will be changed during the iteration
+ */
+ private var baseNS: String? = null
+
+ /**
+ * flag to indicate that skipSiblings() has been called.
+ */
+ private var skipSiblings = false
+
+ /**
+ * flag to indicate that skipSubtree() has been called.
+ */
+ private var skipSubtree = false
+
+ /**
+ * the node iterator doing the work
+ */
+ private var nodeIterator: Iterator? = null
+
+ /**
+ * Constructor with optionsl initial values. If `propName` is provided,
+ * `schemaNS` has also be provided.
+ *
+ * @param xmp the iterated metadata object.
+ * @param schemaNS the iteration is reduced to this schema (optional)
+ * @param propPath the iteration is redurce to this property within the `schemaNS`
+ * @param options advanced iteration options, see [IteratorOptions]
+ *
+ */
+ init {
+
+ // make sure that options is defined at least with defaults
+ this.options = options ?: IteratorOptions()
+
+ // the start node of the iteration depending on the schema and property filter
+ var startNode: XMPNode? = null
+ var initialPath: String? = null
+ val baseSchema = schemaNS != null && schemaNS.length > 0
+ val baseProperty = propPath != null && propPath.length > 0
+
+ when {
+
+ !baseSchema && !baseProperty -> {
+
+ // complete tree will be iterated
+ startNode = xmp.root
+ }
+
+ baseSchema && baseProperty -> {
+
+ // Schema and property node provided
+
+ val path = expandXPath(schemaNS, propPath)
+
+ // base path is the prop path without the property leaf
+ val basePath = XMPPath()
+
+ for (i in 0 until path.size() - 1)
+ basePath.add(path.getSegment(i))
+
+ startNode = findNode(xmp.root, path, false, null)
+ baseNS = schemaNS
+ initialPath = basePath.toString()
+ }
+
+ baseSchema && !baseProperty -> {
+
+ // Only Schema provided
+ startNode = findSchemaNode(xmp.root, schemaNS, false)
+ }
+
+ else -> {
+
+ // !baseSchema && baseProperty
+ // No schema but property provided -> error
+ throw XMPException("Schema namespace URI is required", XMPError.BADSCHEMA)
+ }
+ }
+
+ // create iterator
+ if (startNode != null) {
+
+ if (!this.options.isJustChildren())
+ nodeIterator = NodeIterator(startNode, initialPath, 1)
+ else
+ nodeIterator = NodeIteratorChildren(startNode, initialPath)
+
+ } else
+ nodeIterator = emptySequence().iterator()
+ }
+
+ override fun skipSubtree() {
+ skipSubtree = true
+ }
+
+ override fun skipSiblings() {
+ skipSubtree()
+ skipSiblings = true
+ }
+
+ override fun hasNext(): Boolean =
+ nodeIterator!!.hasNext()
+
+ override fun next(): XMPPropertyInfo =
+ nodeIterator!!.next()
+
+ /**
+ * The `XMPIterator` implementation.
+ * It first returns the node itself, then recursivly the children and qualifier of the node.
+ */
+ private open inner class NodeIterator : Iterator {
+
+ /**
+ * the state of the iteration
+ */
+ private var state = ITERATE_NODE
+
+ /**
+ * the currently visited node
+ */
+ private var visitedNode: XMPNode? = null
+
+ /**
+ * the recursively accumulated path
+ */
+ private var path: String? = null
+
+ /**
+ * the iterator that goes through the children and qualifier list
+ */
+ protected var childrenIterator: Iterator? = null
+
+ /**
+ * index of node with parent, only interesting for arrays
+ */
+ private var index = 0
+
+ /**
+ * the iterator for each child
+ */
+ private var subIterator = emptySequence().iterator()
+
+ /**
+ * the cached `PropertyInfo` to return
+ */
+ protected var returnProperty: XMPPropertyInfo? = null
+
+ /**
+ * Default constructor
+ */
+ constructor()
+
+ /**
+ * Constructor for the node iterator.
+ *
+ * @param visitedNode the currently visited node
+ * @param parentPath the accumulated path of the node
+ * @param index the index within the parent node (only for arrays)
+ */
+ constructor(visitedNode: XMPNode, parentPath: String?, index: Int) {
+
+ this.visitedNode = visitedNode
+ state = ITERATE_NODE
+
+ if (visitedNode.options.isSchemaNode())
+ baseNS = visitedNode.name
+
+ // for all but the root node and schema nodes
+ path = accumulatePath(visitedNode, parentPath, index)
+ }
+
+ /**
+ * Prepares the next node to return if not already done.
+ *
+ * @see Iterator.hasNext
+ */
+ override fun hasNext(): Boolean {
+
+ if (returnProperty != null)
+ return true // hasNext has been called before
+
+ // find next node
+ return if (state == ITERATE_NODE) {
+
+ reportNode()
+
+ } else if (state == ITERATE_CHILDREN) {
+
+ if (childrenIterator == null)
+ childrenIterator = visitedNode!!.iterateChildren()
+
+ var hasNext = iterateChildren(childrenIterator!!)
+
+ if (!hasNext && visitedNode!!.hasQualifier() && !options.isOmitQualifiers()) {
+ state = ITERATE_QUALIFIER
+ childrenIterator = null
+ hasNext = hasNext()
+ }
+
+ hasNext
+
+ } else {
+
+ if (childrenIterator == null)
+ childrenIterator = visitedNode!!.iterateQualifier()
+
+ iterateChildren(childrenIterator!!)
+ }
+ }
+
+ /**
+ * Sets the returnProperty as next item or recurses into `hasNext()`.
+ *
+ * @return Returns if there is a next item to return.
+ */
+ protected fun reportNode(): Boolean {
+
+ state = ITERATE_CHILDREN
+
+ return if (visitedNode!!.parent != null &&
+ (!options.isJustLeafnodes() || !visitedNode!!.hasChildren())
+ ) {
+ returnProperty = createPropertyInfo(visitedNode, baseNS!!, path!!)
+ true
+ } else {
+ hasNext()
+ }
+ }
+
+ /**
+ * Handles the iteration of the children or qualfier
+ *
+ * @return Returns if there are more elements available.
+ */
+ private fun iterateChildren(iterator: Iterator): Boolean {
+
+ if (skipSiblings) {
+
+ skipSiblings = false
+
+ subIterator = emptySequence().iterator()
+ }
+
+ /*
+ * Create sub iterator for every child, if its the first child
+ * visited or the former child is finished
+ */
+ if (!subIterator.hasNext() && iterator.hasNext()) {
+
+ val child = iterator.next()
+
+ index++
+
+ subIterator = NodeIterator(child, path, index)
+ }
+
+ if (subIterator.hasNext()) {
+
+ returnProperty = subIterator.next()
+
+ /* We have more available */
+ return true
+ }
+
+ /* There are no more children - end iteration. */
+ return false
+ }
+
+ /**
+ * Calls hasNext() and returnes the prepared node. Afterward its set to null.
+ * The existance of returnProperty indicates if there is a next node, otherwise
+ * an exceptio is thrown.
+ *
+ * @see Iterator.next
+ */
+ override fun next(): XMPPropertyInfo {
+
+ if (!hasNext())
+ throw NoSuchElementException("There are no more nodes to return")
+
+ val result = returnProperty
+
+ returnProperty = null
+
+ return result!!
+ }
+
+ /**
+ * @param currNode the node that will be added to the path.
+ * @param parentPath the path up to this node.
+ * @param currentIndex the current array index if an arrey is traversed
+ * @return Returns the updated path.
+ */
+ protected fun accumulatePath(currNode: XMPNode, parentPath: String?, currentIndex: Int): String? {
+
+ val separator: String
+ val segmentName: String?
+
+ if (currNode.parent == null || currNode.options.isSchemaNode()) {
+ return null
+ } else if (currNode.parent!!.options.isArray()) {
+ separator = ""
+ segmentName = "[$currentIndex]"
+ } else {
+ separator = "/"
+ segmentName = currNode.name
+ }
+
+ return if (parentPath == null || parentPath.length == 0) {
+
+ segmentName
+
+ } else if (options.isJustLeafname()) {
+
+ if (!segmentName!!.startsWith("?"))
+ segmentName
+ else
+ segmentName.substring(1) // qualifier
+
+ } else {
+
+ parentPath + separator + segmentName
+ }
+ }
+
+ /**
+ * Creates a property info object from an `XMPNode`.
+ *
+ * @param node an `XMPNode`
+ * @param baseNS the base namespace to report
+ * @param path the full property path
+ * @return Returns a `XMPProperty`-object that serves representation of the node.
+ */
+ protected fun createPropertyInfo(
+ node: XMPNode?,
+ baseNS: String,
+ path: String
+ ): XMPPropertyInfo {
+
+ val value = if (node!!.options.isSchemaNode())
+ null
+ else
+ node.value
+
+ return object : XMPPropertyInfo {
+
+ override fun getNamespace(): String {
+
+ if (node.options.isSchemaNode())
+ return baseNS
+
+ // determine namespace of leaf node
+ val qname = QName(node.name!!)
+
+ return schemaRegistry.getNamespaceURI(qname.prefix!!)!!
+ }
+
+ override fun getPath(): String = path
+
+ override fun getValue(): String = value!!
+
+ override fun getOptions(): PropertyOptions = node.options
+
+ // the language is not reported
+ override fun getLanguage(): String? = null
+ }
+ }
+ }
+
+ /**
+ * This iterator is derived from the default `NodeIterator`,
+ * and is only used for the option [IteratorOptions.JUST_CHILDREN].
+ */
+ private inner class NodeIteratorChildren(parentNode: XMPNode, parentPath: String?) : NodeIterator() {
+
+ private val parentPath: String
+
+ private val nodeChildrenIterator: Iterator
+
+ private var index = 0
+
+ /**
+ * Constructor
+ *
+ * @param parentNode the node which children shall be iterated.
+ * @param parentPath the full path of the former node without the leaf node.
+ */
+ init {
+
+ if (parentNode.options.isSchemaNode())
+ baseNS = parentNode.name
+
+ this.parentPath = accumulatePath(parentNode, parentPath, 1)!!
+
+ nodeChildrenIterator = parentNode.iterateChildren()
+ }
+
+ /**
+ * Prepares the next node to return if not already done.
+ *
+ * @see Iterator.hasNext
+ */
+ override fun hasNext(): Boolean {
+
+ return if (returnProperty != null) {
+
+ // hasNext has been called before
+ true
+
+ } else if (skipSiblings) {
+
+ false
+
+ } else if (nodeChildrenIterator.hasNext()) {
+
+ val child = nodeChildrenIterator.next()
+
+ index++
+
+ var path: String? = null
+
+ if (child.options.isSchemaNode()) {
+ baseNS = child.name
+ } else if (child.parent != null) {
+ // for all but the root node and schema nodes
+ path = accumulatePath(child, parentPath, index)
+ }
+
+ // report next property, skip not-leaf nodes in case options is set
+ if (!options.isJustLeafnodes() || !child.hasChildren()) {
+ returnProperty = createPropertyInfo(child, baseNS!!, path!!)
+ true
+ } else {
+ hasNext()
+ }
+
+ } else {
+ false
+ }
+ }
+ }
+
+ companion object {
+
+ /**
+ * iteration state
+ */
+ const val ITERATE_NODE = 0
+
+ /**
+ * iteration state
+ */
+ const val ITERATE_CHILDREN = 1
+
+ /**
+ * iteration state
+ */
+ const val ITERATE_QUALIFIER = 2
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPMetaImpl.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPMetaImpl.kt
new file mode 100644
index 0000000..0774a82
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPMetaImpl.kt
@@ -0,0 +1,954 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPIterator
+import com.ashampoo.xmp.XMPMeta
+import com.ashampoo.xmp.XMPPathFactory.composeArrayItemPath
+import com.ashampoo.xmp.XMPPathFactory.composeQualifierPath
+import com.ashampoo.xmp.XMPPathFactory.composeStructFieldPath
+import com.ashampoo.xmp.XMPUtils.convertToBoolean
+import com.ashampoo.xmp.XMPUtils.convertToDouble
+import com.ashampoo.xmp.XMPUtils.convertToInteger
+import com.ashampoo.xmp.XMPUtils.convertToLong
+import com.ashampoo.xmp.XMPUtils.decodeBase64
+import com.ashampoo.xmp.impl.Utils.normalizeLangValue
+import com.ashampoo.xmp.impl.XMPNodeUtils.appendLangItem
+import com.ashampoo.xmp.impl.XMPNodeUtils.chooseLocalizedText
+import com.ashampoo.xmp.impl.XMPNodeUtils.deleteNode
+import com.ashampoo.xmp.impl.XMPNodeUtils.findNode
+import com.ashampoo.xmp.impl.XMPNodeUtils.setNodeValue
+import com.ashampoo.xmp.impl.XMPNodeUtils.verifySetOptions
+import com.ashampoo.xmp.impl.XMPNormalizer.normalize
+import com.ashampoo.xmp.impl.xpath.XMPPathParser.expandXPath
+import com.ashampoo.xmp.options.IteratorOptions
+import com.ashampoo.xmp.options.ParseOptions
+import com.ashampoo.xmp.options.PropertyOptions
+import com.ashampoo.xmp.properties.XMPProperty
+
+/**
+ * Implementation for [XMPMeta].
+ */
+class XMPMetaImpl : XMPMeta {
+
+ /**
+ * root of the metadata tree
+ */
+ var root: XMPNode
+ private set
+
+ /**
+ * the xpacket processing instructions content
+ */
+ private var packetHeader: String? = null
+
+ /**
+ * Constructor for an empty metadata object.
+ */
+ constructor() {
+ // create root node
+ this.root = XMPNode(null, null, PropertyOptions())
+ }
+
+ constructor(tree: XMPNode) {
+ this.root = tree
+ }
+
+ override fun appendArrayItem(
+ schemaNS: String,
+ arrayName: String,
+ arrayOptions: PropertyOptions,
+ itemValue: String,
+ itemOptions: PropertyOptions
+ ) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (arrayName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ if (!arrayOptions.isOnlyArrayOptions())
+ throw XMPException("Only array form flags allowed for arrayOptions", XMPError.BADOPTIONS)
+
+ // Check if array options are set correctly.
+ val verifiedArrayOptions = verifySetOptions(arrayOptions, null)
+
+ // Locate or create the array. If it already exists, make sure the array form from the options
+ // parameter is compatible with the current state.
+ val arrayPath = expandXPath(schemaNS, arrayName)
+
+ // Just lookup, don't try to create.
+ var arrayNode = findNode(this.root, arrayPath, false, null)
+
+ if (arrayNode != null) {
+
+ // The array exists, make sure the form is compatible. Zero arrayForm means take what exists.
+ if (!arrayNode.options.isArray())
+ throw XMPException("The named property is not an array", XMPError.BADXPATH)
+
+ } else {
+
+ // The array does not exist, try to create it.
+ if (verifiedArrayOptions.isArray()) {
+
+ arrayNode = findNode(this.root, arrayPath, true, verifiedArrayOptions)
+
+ if (arrayNode == null)
+ throw XMPException("Failure creating array node", XMPError.BADXPATH)
+
+ } else {
+
+ // array options missing
+ throw XMPException("Explicit arrayOptions required to create new array", XMPError.BADOPTIONS)
+ }
+ }
+
+ doSetArrayItem(arrayNode, XMPConst.ARRAY_LAST_ITEM, itemValue, itemOptions, true)
+ }
+
+ override fun countArrayItems(schemaNS: String, arrayName: String): Int {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (arrayName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val arrayPath = expandXPath(schemaNS, arrayName)
+ val arrayNode = findNode(this.root, arrayPath, false, null) ?: return 0
+
+ if (!arrayNode.options.isArray())
+ throw XMPException("The named property is not an array", XMPError.BADXPATH)
+
+ return arrayNode.getChildrenLength()
+ }
+
+ override fun deleteArrayItem(schemaNS: String, arrayName: String, itemIndex: Int) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (arrayName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val itemPath = composeArrayItemPath(arrayName, itemIndex)
+
+ deleteProperty(schemaNS, itemPath)
+ }
+
+ override fun deleteProperty(schemaNS: String, propName: String) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val propNode = findNode(
+ xmpTree = this.root,
+ xpath = expandXPath(schemaNS, propName),
+ createNodes = false,
+ leafOptions = null
+ ) ?: return
+
+ deleteNode(propNode)
+ }
+
+ override fun deleteQualifier(schemaNS: String, propName: String, qualNS: String, qualName: String) {
+
+ // Note: qualNS and qualName are checked inside composeQualfierPath
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val qualPath = propName + composeQualifierPath(qualNS, qualName)
+
+ deleteProperty(schemaNS, qualPath)
+ }
+
+ override fun deleteStructField(
+ schemaNS: String,
+ structName: String,
+ fieldNS: String,
+ fieldName: String
+ ) {
+
+ // fieldNS and fieldName are checked inside composeStructFieldPath
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (structName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val fieldPath = structName + composeStructFieldPath(fieldNS, fieldName)
+
+ deleteProperty(schemaNS, fieldPath)
+ }
+
+ override fun doesPropertyExist(schemaNS: String, propName: String): Boolean {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val propNode = findNode(
+ xmpTree = this.root,
+ xpath = expandXPath(schemaNS, propName),
+ createNodes = false,
+ leafOptions = null
+ )
+
+ return propNode != null
+ }
+
+ override fun doesArrayItemExist(schemaNS: String, arrayName: String, itemIndex: Int): Boolean {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (arrayName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val path = composeArrayItemPath(arrayName, itemIndex)
+
+ return doesPropertyExist(schemaNS, path)
+ }
+
+ override fun doesStructFieldExist(
+ schemaNS: String,
+ structName: String,
+ fieldNS: String,
+ fieldName: String
+ ): Boolean {
+
+ // fieldNS and fieldName are checked inside composeStructFieldPath()
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (structName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val path = composeStructFieldPath(fieldNS, fieldName)
+
+ return doesPropertyExist(schemaNS, structName + path)
+ }
+
+ override fun doesQualifierExist(
+ schemaNS: String,
+ propName: String,
+ qualNS: String,
+ qualName: String
+ ): Boolean {
+
+ // qualNS and qualName are checked inside composeQualifierPath()
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val path = composeQualifierPath(qualNS, qualName)
+
+ return doesPropertyExist(schemaNS, propName + path)
+ }
+
+ override fun getArrayItem(schemaNS: String, arrayName: String, itemIndex: Int): XMPProperty? {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (arrayName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val itemPath = composeArrayItemPath(arrayName, itemIndex)
+
+ return getProperty(schemaNS, itemPath)
+ }
+
+ override fun getLocalizedText(
+ schemaNS: String,
+ altTextName: String,
+ genericLang: String?,
+ specificLang: String
+ ): XMPProperty? {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (altTextName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ if (specificLang.isEmpty())
+ throw XMPException("Empty specific language", XMPError.BADPARAM)
+
+ val normalizedGenericLang = if (genericLang != null) normalizeLangValue(genericLang) else null
+ val normalizedSpecificLang = normalizeLangValue(specificLang)
+
+ val arrayPath = expandXPath(schemaNS, altTextName)
+
+ // *** This expand/find idiom is used in 3 Getters.
+ val arrayNode = findNode(this.root, arrayPath, false, null) ?: return null
+ val result = chooseLocalizedText(arrayNode, normalizedGenericLang, normalizedSpecificLang)
+ val match = result[0] as Int
+ val itemNode = result[1] as? XMPNode
+
+ return if (match != XMPNodeUtils.CLT_NO_VALUES) {
+
+ object : XMPProperty {
+ override fun getValue(): String {
+ return itemNode!!.value!!
+ }
+
+ override fun getOptions(): PropertyOptions {
+ return itemNode!!.options
+ }
+
+ override fun getLanguage(): String {
+ return itemNode!!.getQualifier(1).value!!
+ }
+
+ override fun toString(): String {
+ return itemNode!!.value.toString()
+ }
+ }
+
+ } else {
+ null
+ }
+ }
+
+ override fun setLocalizedText(
+ schemaNS: String,
+ altTextName: String,
+ genericLang: String?,
+ specificLang: String,
+ itemValue: String,
+ options: PropertyOptions
+ ) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (altTextName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ if (specificLang.isEmpty())
+ throw XMPException("Empty specific language", XMPError.BADPARAM)
+
+ val normalizedGenericLang = if (genericLang != null) normalizeLangValue(genericLang) else null
+ val normalizedSpecificLang = normalizeLangValue(specificLang)
+
+ val arrayPath = expandXPath(schemaNS, altTextName)
+
+ // Find the array node and set the options if it was just created.
+ val arrayNode = findNode(
+ this.root, arrayPath, true,
+ PropertyOptions(
+ PropertyOptions.ARRAY or PropertyOptions.ARRAY_ORDERED
+ or PropertyOptions.ARRAY_ALTERNATE or PropertyOptions.ARRAY_ALT_TEXT
+ )
+ )
+
+ if (arrayNode == null) {
+
+ throw XMPException("Failed to find or create array node", XMPError.BADXPATH)
+
+ } else if (!arrayNode.options.isArrayAltText()) {
+
+ if (!arrayNode.hasChildren() && arrayNode.options.isArrayAlternate())
+ arrayNode.options.setArrayAltText(true)
+ else
+ throw XMPException("Specified property is no alt-text array", XMPError.BADXPATH)
+ }
+
+ // Make sure the x-default item, if any, is first.
+ var haveXDefault = false
+ var xdItem: XMPNode? = null
+
+ for (item in arrayNode.iterateChildren()) {
+
+ if (!item.hasQualifier() || XMPConst.XML_LANG != item.getQualifier(1).name)
+ throw XMPException("Language qualifier must be first", XMPError.BADXPATH)
+
+ if (XMPConst.X_DEFAULT == item.getQualifier(1).value) {
+ xdItem = item
+ haveXDefault = true
+ break
+ }
+ }
+
+ // Moves x-default to the beginning of the array
+ if (xdItem != null && arrayNode.getChildrenLength() > 1) {
+
+ arrayNode.removeChild(xdItem)
+ arrayNode.addChild(1, xdItem)
+ }
+
+ // Find the appropriate item.
+ // chooseLocalizedText will make sure the array is a language alternative.
+ val result = chooseLocalizedText(arrayNode, normalizedGenericLang, normalizedSpecificLang)
+ val match = result[0] as Int
+ val itemNode = result[1] as? XMPNode
+
+ val specificXDefault = XMPConst.X_DEFAULT == normalizedSpecificLang
+
+ when (match) {
+
+ XMPNodeUtils.CLT_NO_VALUES -> {
+
+ // Create the array items for the specificLang and x-default, with x-default first.
+ appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue)
+
+ haveXDefault = true
+
+ if (!specificXDefault)
+ appendLangItem(arrayNode, normalizedSpecificLang, itemValue)
+ }
+
+ XMPNodeUtils.CLT_SPECIFIC_MATCH -> if (!specificXDefault) {
+
+ // Update the specific item, update x-default if it matches the old value.
+ if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.value == itemNode!!.value)
+ xdItem.value = itemValue
+
+ // ! Do this after the x-default check!
+ itemNode!!.value = itemValue
+
+ } else {
+
+ // Update all items whose values match the old x-default value.
+ check(haveXDefault && xdItem == itemNode)
+
+ val it = arrayNode.iterateChildren()
+
+ while (it.hasNext()) {
+
+ val currItem = it.next()
+
+ if (currItem == xdItem || currItem.value != xdItem?.value)
+ continue
+
+ currItem.value = itemValue
+ }
+
+ // And finally do the x-default item.
+ if (xdItem != null)
+ xdItem.value = itemValue
+ }
+
+ XMPNodeUtils.CLT_SINGLE_GENERIC -> {
+
+ // Update the generic item, update x-default if it matches the old value.
+ if (haveXDefault && xdItem != itemNode && xdItem != null && xdItem.value == itemNode!!.value)
+ xdItem.value = itemValue
+
+ // ! Do this after the x-default check!
+ itemNode!!.value = itemValue
+ }
+
+ XMPNodeUtils.CLT_FIRST_ITEM, XMPNodeUtils.CLT_MULTIPLE_GENERIC -> {
+
+ // Create the specific language, ignore x-default.
+ appendLangItem(arrayNode, normalizedSpecificLang, itemValue)
+
+ if (specificXDefault) haveXDefault = true
+ }
+
+ XMPNodeUtils.CLT_XDEFAULT -> {
+
+ // Create the specific language, update x-default if it was the only item.
+ if (xdItem != null && arrayNode.getChildrenLength() == 1)
+ xdItem.value = itemValue
+
+ appendLangItem(arrayNode, normalizedSpecificLang, itemValue)
+ }
+
+ else -> // does not happen under normal circumstances
+ throw XMPException("Unexpected result from ChooseLocalizedText", XMPError.INTERNALFAILURE)
+ }
+
+ // Add an x-default at the front if needed.
+ if (!haveXDefault && arrayNode.getChildrenLength() == 1)
+ appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue)
+ }
+
+ override fun getProperty(schemaNS: String, propName: String): XMPProperty? =
+ getProperty(schemaNS, propName, VALUE_STRING)
+
+ /**
+ * Returns a property, but the result value can be requested. It can be one
+ * of [XMPMetaImpl.VALUE_STRING], [XMPMetaImpl.VALUE_BOOLEAN],
+ * [XMPMetaImpl.VALUE_INTEGER], [XMPMetaImpl.VALUE_LONG],
+ * [XMPMetaImpl.VALUE_DOUBLE], [XMPMetaImpl.VALUE_DATE],
+ * [XMPMetaImpl.VALUE_TIME_IN_MILLIS], [XMPMetaImpl.VALUE_BASE64].
+ */
+ private fun getProperty(schemaNS: String, propName: String, valueType: Int): XMPProperty? {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val propNode = findNode(
+ xmpTree = this.root,
+ xpath = expandXPath(schemaNS, propName),
+ createNodes = false,
+ leafOptions = null
+ ) ?: return null
+
+ if (valueType != VALUE_STRING && propNode.options.isCompositeProperty())
+ throw XMPException("Property must be simple when a value type is requested", XMPError.BADXPATH)
+
+ val value = evaluateNodeValue(valueType, propNode)
+
+ return object : XMPProperty {
+
+ override fun getValue(): String? {
+ return value?.toString()
+ }
+
+ override fun getOptions(): PropertyOptions {
+ return propNode.options
+ }
+
+ override fun getLanguage(): String? {
+ return null
+ }
+
+ override fun toString(): String {
+ return value.toString()
+ }
+ }
+ }
+
+ /**
+ * Returns a property, but the result value can be requested.
+ */
+ private fun getPropertyObject(schemaNS: String, propName: String, valueType: Int): Any? {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val propNode = findNode(
+ xmpTree = this.root,
+ xpath = expandXPath(schemaNS, propName),
+ createNodes = false,
+ leafOptions = null
+ ) ?: return null
+
+ if (valueType != VALUE_STRING && propNode.options.isCompositeProperty())
+ throw XMPException("Property must be simple when a value type is requested", XMPError.BADXPATH)
+
+ return evaluateNodeValue(valueType, propNode)
+ }
+
+ override fun getPropertyBoolean(schemaNS: String, propName: String): Boolean? =
+ getPropertyObject(schemaNS, propName, VALUE_BOOLEAN) as? Boolean
+
+ override fun setPropertyBoolean(
+ schemaNS: String,
+ propName: String,
+ propValue: Boolean,
+ options: PropertyOptions
+ ) {
+ setProperty(
+ schemaNS,
+ propName,
+ if (propValue) XMPConst.TRUESTR else XMPConst.FALSESTR,
+ options
+ )
+ }
+
+ override fun getPropertyInteger(schemaNS: String, propName: String): Int? =
+ getPropertyObject(schemaNS, propName, VALUE_INTEGER) as? Int
+
+ override fun setPropertyInteger(
+ schemaNS: String,
+ propName: String,
+ propValue: Int,
+ options: PropertyOptions
+ ) {
+ setProperty(schemaNS, propName, propValue, options)
+ }
+
+ override fun getPropertyLong(schemaNS: String, propName: String): Long? =
+ getPropertyObject(schemaNS, propName, VALUE_LONG) as? Long
+
+ override fun setPropertyLong(
+ schemaNS: String,
+ propName: String,
+ propValue: Long,
+ options: PropertyOptions
+ ) {
+ setProperty(schemaNS, propName, propValue, options)
+ }
+
+ override fun getPropertyDouble(schemaNS: String, propName: String): Double? =
+ getPropertyObject(schemaNS, propName, VALUE_DOUBLE) as? Double
+
+ override fun setPropertyDouble(
+ schemaNS: String,
+ propName: String,
+ propValue: Double,
+ options: PropertyOptions
+ ) {
+ setProperty(schemaNS, propName, propValue, options)
+ }
+
+ override fun getPropertyBase64(schemaNS: String, propName: String): ByteArray? =
+ getPropertyObject(schemaNS, propName, VALUE_BASE64) as? ByteArray
+
+ override fun getPropertyString(schemaNS: String, propName: String): String? =
+ getPropertyObject(schemaNS, propName, VALUE_STRING) as? String
+
+ override fun setPropertyBase64(
+ schemaNS: String,
+ propName: String,
+ propValue: ByteArray,
+ options: PropertyOptions
+ ) {
+ setProperty(schemaNS, propName, propValue, options)
+ }
+
+ override fun getQualifier(
+ schemaNS: String,
+ propName: String,
+ qualNS: String,
+ qualName: String
+ ): XMPProperty? {
+
+ // qualNS and qualName are checked inside composeQualfierPath
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val qualPath = propName + composeQualifierPath(qualNS, qualName)
+
+ return getProperty(schemaNS, qualPath)
+ }
+
+ override fun getStructField(
+ schemaNS: String,
+ structName: String,
+ fieldNS: String,
+ fieldName: String
+ ): XMPProperty? {
+
+ // fieldNS and fieldName are checked inside composeStructFieldPath
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (structName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val fieldPath = structName + composeStructFieldPath(fieldNS, fieldName)
+
+ return getProperty(schemaNS, fieldPath)
+ }
+
+ override fun iterator(): XMPIterator =
+ iterator(IteratorOptions())
+
+ override fun iterator(options: IteratorOptions): com.ashampoo.xmp.XMPIterator =
+ iterator(null, null, options)
+
+ override fun iterator(
+ schemaNS: String?,
+ propName: String?,
+ options: IteratorOptions
+ ): XMPIterator =
+ XMPIteratorImpl(this, schemaNS, propName, options)
+
+ override fun setArrayItem(
+ schemaNS: String,
+ arrayName: String,
+ itemIndex: Int,
+ itemValue: String,
+ options: PropertyOptions
+ ) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (arrayName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ // Just lookup, don't try to create.
+ val arrayPath = expandXPath(schemaNS, arrayName)
+ val arrayNode = findNode(this.root, arrayPath, false, null)
+
+ if (arrayNode == null)
+ throw XMPException("Specified array does not exist", XMPError.BADXPATH)
+
+ doSetArrayItem(arrayNode, itemIndex, itemValue, options, false)
+ }
+
+ override fun insertArrayItem(
+ schemaNS: String,
+ arrayName: String,
+ itemIndex: Int,
+ itemValue: String,
+ options: PropertyOptions
+ ) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (arrayName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ // Just lookup, don't try to create.
+ val arrayPath = expandXPath(schemaNS, arrayName)
+ val arrayNode = findNode(this.root, arrayPath, false, null)
+
+ if (arrayNode == null)
+ throw XMPException("Specified array does not exist", XMPError.BADXPATH)
+
+ doSetArrayItem(arrayNode, itemIndex, itemValue, options, true)
+ }
+
+ override fun setProperty(
+ schemaNS: String,
+ propName: String,
+ propValue: Any?,
+ options: PropertyOptions
+ ) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ val verifiedOptions = verifySetOptions(options, propValue)
+
+ val propNode = findNode(
+ xmpTree = this.root,
+ xpath = expandXPath(schemaNS, propName),
+ createNodes = true,
+ leafOptions = verifySetOptions(options, propValue)
+ ) ?: throw XMPException("Specified property does not exist", XMPError.BADXPATH)
+
+ setNode(propNode, propValue, verifiedOptions, false)
+ }
+
+ override fun setQualifier(
+ schemaNS: String,
+ propName: String,
+ qualNS: String,
+ qualName: String,
+ qualValue: String,
+ options: PropertyOptions
+ ) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (propName.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ if (!doesPropertyExist(schemaNS, propName))
+ throw XMPException("Specified property does not exist!", XMPError.BADXPATH)
+
+ val qualPath = propName + composeQualifierPath(qualNS, qualName)
+
+ setProperty(schemaNS, qualPath, qualValue, options)
+ }
+
+ override fun setStructField(
+ schemaNS: String,
+ structName: String,
+ fieldNS: String,
+ fieldName: String,
+ fieldValue: String?,
+ options: PropertyOptions
+ ) {
+
+ if (schemaNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (structName.isEmpty())
+ throw XMPException("Empty array name", XMPError.BADPARAM)
+
+ val fieldPath = structName + composeStructFieldPath(fieldNS, fieldName)
+
+ setProperty(schemaNS, fieldPath, fieldValue, options)
+ }
+
+ override fun getObjectName(): String =
+ root.name ?: ""
+
+ override fun setObjectName(name: String) {
+ root.name = name
+ }
+
+ override fun getPacketHeader(): String? =
+ packetHeader
+
+ /**
+ * Sets the packetHeader attributes, only used by the parser.
+ */
+ fun setPacketHeader(packetHeader: String?) {
+ this.packetHeader = packetHeader
+ }
+
+ override fun sort() {
+ this.root.sort()
+ }
+
+ override fun normalize(options: ParseOptions) {
+ normalize(this, options)
+ }
+
+ // -------------------------------------------------------------------------------------
+ // private
+
+ /**
+ * Locate or create the item node and set the value. Note the index
+ * parameter is one-based! The index can be in the range [1..size + 1] or
+ * "last()", normalize it and check the insert flags. The order of the
+ * normalization checks is important. If the array is empty we end up with
+ * an index and location to set item size + 1.
+ */
+ private fun doSetArrayItem(
+ arrayNode: XMPNode,
+ itemIndex: Int,
+ itemValue: String,
+ itemOptions: PropertyOptions,
+ insert: Boolean
+ ) {
+
+ val itemNode = XMPNode(XMPConst.ARRAY_ITEM_NAME, null)
+
+ val verifiedItemOptions = verifySetOptions(itemOptions, itemValue)
+
+ // in insert mode the index after the last is allowed,
+ // even ARRAY_LAST_ITEM points to the index *after* the last.
+ val maxIndex = if (insert)
+ arrayNode.getChildrenLength() + 1
+ else
+ arrayNode.getChildrenLength()
+
+ val limitedItemIndex = if (itemIndex == XMPConst.ARRAY_LAST_ITEM)
+ maxIndex
+ else
+ itemIndex
+
+ if (1 <= limitedItemIndex && limitedItemIndex <= maxIndex) {
+
+ if (!insert)
+ arrayNode.removeChild(limitedItemIndex)
+
+ arrayNode.addChild(limitedItemIndex, itemNode)
+ setNode(itemNode, itemValue, verifiedItemOptions, false)
+
+ } else {
+ throw XMPException("Array index out of bounds", XMPError.BADINDEX)
+ }
+ }
+
+ /**
+ * The internals for setProperty() and related calls, used after the node is found or created.
+ */
+ fun setNode(node: XMPNode, value: Any?, newOptions: PropertyOptions, deleteExisting: Boolean) {
+
+ val compositeMask = PropertyOptions.ARRAY or PropertyOptions.ARRAY_ALT_TEXT or
+ PropertyOptions.ARRAY_ALTERNATE or PropertyOptions.ARRAY_ORDERED or PropertyOptions.STRUCT
+
+ if (deleteExisting)
+ node.clear()
+
+ // its checked by setOptions(), if the merged result is a valid options set
+ node.options.mergeWith(newOptions)
+
+ if (node.options.getOptions() and compositeMask == 0) {
+
+ // This is setting the value of a leaf node.
+ setNodeValue(node, value)
+
+ } else {
+
+ if (value != null && value.toString().length > 0)
+ throw XMPException("Composite nodes can't have values", XMPError.BADXPATH)
+
+ // Can't change an array to a struct, or vice versa.
+ if (node.options.getOptions() and compositeMask != 0)
+ if (newOptions.getOptions() and compositeMask != node.options.getOptions() and compositeMask)
+ throw XMPException("Requested and existing composite form mismatch", XMPError.BADXPATH)
+
+ node.removeChildren()
+ }
+ }
+
+ /**
+ * Evaluates a raw node value to the given value type, apply special
+ * conversions for defined types in XMP.
+ */
+ private fun evaluateNodeValue(valueType: Int, propNode: XMPNode): Any? {
+
+ val value: Any?
+ val rawValue = propNode.value
+
+ value = when (valueType) {
+
+ VALUE_BOOLEAN -> convertToBoolean(rawValue)
+
+ VALUE_INTEGER -> convertToInteger(rawValue)
+
+ VALUE_LONG -> convertToLong(rawValue)
+
+ VALUE_DOUBLE -> convertToDouble(rawValue)
+
+ VALUE_BASE64 -> decodeBase64(rawValue!!)
+
+ // leaf values return empty string instead of null
+ // for the other cases the converter methods provides a "null" value.
+ // a default value can only occur if this method is made public.
+ VALUE_STRING ->
+ if (rawValue != null || propNode.options.isCompositeProperty()) rawValue else ""
+
+ else ->
+ if (rawValue != null || propNode.options.isCompositeProperty()) rawValue else ""
+ }
+
+ return value
+ }
+
+ companion object {
+
+ /**
+ * Property values are Strings by default
+ */
+
+ private const val VALUE_STRING = 0
+ private const val VALUE_BOOLEAN = 1
+ private const val VALUE_INTEGER = 2
+ private const val VALUE_LONG = 3
+ private const val VALUE_DOUBLE = 4
+ private const val VALUE_BASE64 = 7
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPMetaParser.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPMetaParser.kt
new file mode 100644
index 0000000..3007b5f
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPMetaParser.kt
@@ -0,0 +1,157 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPMeta
+import com.ashampoo.xmp.impl.XMPNormalizer.normalize
+import com.ashampoo.xmp.options.ParseOptions
+import nl.adaptivity.xmlutil.dom.Element
+import nl.adaptivity.xmlutil.dom.Node
+import nl.adaptivity.xmlutil.dom.ProcessingInstruction
+import nl.adaptivity.xmlutil.dom.Text
+import nl.adaptivity.xmlutil.dom.childNodes
+import nl.adaptivity.xmlutil.dom.getData
+import nl.adaptivity.xmlutil.dom.getTarget
+import nl.adaptivity.xmlutil.dom.length
+import nl.adaptivity.xmlutil.dom.localName
+import nl.adaptivity.xmlutil.dom.namespaceURI
+
+/**
+ * This class replaces the `ExpatAdapter.cpp` and does the
+ * XML-parsing and fixes the prefix. After the parsing several normalisations
+ * are applied to the XMPTree.
+ */
+object XMPMetaParser {
+
+ private val XMP_RDF = Any() // "new Object()" in Java
+
+ /**
+ * Parses the input source into an XMP metadata object, including
+ * de-aliasing and normalisation.
+ *
+ * @param input the XMP string
+ * @param options the parse options
+ * @return Returns the resulting XMP metadata object
+ */
+ fun parse(
+ input: String,
+ options: ParseOptions = ParseOptions()
+ ): XMPMeta {
+
+ val document = XmlUtilDomParser.parseDocumentFromString(input)
+
+ val xmpmetaRequired = options.getRequireXMPMeta()
+
+ var result: Array? = arrayOfNulls(3)
+
+ result = findRootNode(document, xmpmetaRequired, result)
+
+ return if (result != null && result[1] === XMP_RDF) {
+
+ val xmp = XMPRDFParser.parse(result[0] as Node, options)
+
+ xmp.setPacketHeader(result[2] as? String)
+
+ // Check if the XMP object shall be normalized
+ if (!options.getOmitNormalization())
+ normalize(xmp, options)
+ else
+ xmp
+
+ } else {
+
+ /* No appropriate root node found, return empty metadata object */
+ XMPMetaImpl()
+ }
+ }
+
+ /**
+ * Find the XML node that is the root of the XMP data tree. Generally this
+ * will be an outer node, but it could be anywhere if a general XML document
+ * is parsed (e.g. SVG). The XML parser counted all rdf:RDF and
+ * pxmp:XMP_Packet nodes, and kept a pointer to the last one. If there is
+ * more than one possible root use PickBestRoot to choose among them.
+ *
+ * If there is a root node, try to extract the version of the previous XMP
+ * toolkit.
+ *
+ * Pick the first x:xmpmeta among multiple root candidates. If there aren't
+ * any, pick the first bare rdf:RDF if that is allowed. The returned root is
+ * the rdf:RDF child if an x:xmpmeta element was chosen. The search is
+ * breadth first, so a higher level candiate is chosen over a lower level
+ * one that was textually earlier in the serialized XML.
+ *
+ * @param root the root of the xml document
+ * @param xmpmetaRequired flag if the xmpmeta-tag is still required, might be set
+ * initially to `true`, if the parse option "REQUIRE_XMP_META" is set
+ * @param result The result array that is filled during the recursive process.
+ * @return Returns an array that contains the result or `null`.
+ * The array contains:
+ *
+ * * [0] - the rdf:RDF-node
+ * * [1] - an object that is either XMP_RDF or XMP_PLAIN (the latter is decrecated)
+ * * [2] - the body text of the xpacket-instruction.
+ */
+ private fun findRootNode(root: Node, xmpmetaRequired: Boolean, result: Array?): Array? {
+
+ // Look among this parent's content for x:xapmeta or x:xmpmeta.
+ // The recursion for x:xmpmeta is broader than the strictly defined choice,
+ // but gives us smaller code.
+
+ for (index in 0 until root.childNodes.length) {
+
+ val child = root.childNodes.item(index)
+
+ requireNotNull(child)
+
+ if (child is ProcessingInstruction && XMPConst.XMP_PI == child.getTarget()) {
+
+ // Store the processing instructions content
+ if (result != null)
+ result[2] = child.getData()
+
+ } else if (child !is Text && child !is ProcessingInstruction) {
+
+ val childElement = child as Element
+
+ val rootNS = childElement.namespaceURI
+
+ val rootLocal = childElement.localName
+
+ if (
+ (XMPConst.TAG_XMPMETA == rootLocal || XMPConst.TAG_XAPMETA == rootLocal) &&
+ XMPConst.NS_X == rootNS
+ ) {
+
+ // by not passing the RequireXMPMeta-option, the rdf-Node will be valid
+ return findRootNode(child, false, result)
+ }
+
+ if (!xmpmetaRequired && "RDF" == rootLocal && XMPConst.NS_RDF == rootNS) {
+
+ if (result != null) {
+ result[0] = child
+ result[1] = XMP_RDF
+ }
+
+ return result
+ }
+
+ // continue searching
+ val newResult = findRootNode(child, xmpmetaRequired, result)
+
+ return newResult ?: continue
+ }
+ }
+
+ // no appropriate node has been found
+ return null
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNode.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNode.kt
new file mode 100644
index 0000000..5ac5acd
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNode.kt
@@ -0,0 +1,328 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.options.PropertyOptions
+
+/**
+ * A node in the internally XMP tree, which can be a schema node, a property node, an array node,
+ * an array item, a struct node or a qualifier node (without '?').
+ */
+class XMPNode(
+
+ /**
+ * name of the node, contains different information depending of the node kind
+ */
+ var name: String?,
+
+ /**
+ * value of the node, contains different information depending of the node kind
+ */
+ var value: String?,
+
+ /**
+ * options describing the kind of the node
+ */
+ var options: PropertyOptions = PropertyOptions()
+
+) : Comparable {
+
+ var parent: XMPNode? = null
+ private var children: MutableList? = null
+ private var qualifier: MutableList? = null
+
+ /* Internal processing options */
+
+ var isImplicit = false
+ var hasAliases = false
+ var isAlias = false
+ var hasValueChild = false
+
+ fun clear() {
+ name = null
+ value = null
+ options = PropertyOptions()
+ children = null
+ qualifier = null
+ }
+
+ /**
+ * Returns the children or empty list, if there are none.
+ * Will not lazily create the list!
+ */
+ fun getChildren(): List =
+ children ?: emptyList()
+
+ fun getChild(index: Int): XMPNode =
+ getOrCreateChildren()[index - 1]
+
+ fun addChild(node: XMPNode) {
+
+ assertChildNotExisting(node.name!!)
+
+ node.parent = this
+
+ getOrCreateChildren().add(node)
+ }
+
+ fun addChild(index: Int, node: XMPNode) {
+
+ assertChildNotExisting(node.name!!)
+
+ node.parent = this
+
+ getOrCreateChildren().add(index - 1, node)
+ }
+
+ /**
+ * Replaces a node with another one.
+ */
+ fun replaceChild(index: Int, node: XMPNode) {
+
+ node.parent = this
+
+ getOrCreateChildren()[index - 1] = node
+ }
+
+ fun removeChild(itemIndex: Int) {
+
+ getOrCreateChildren().removeAt(itemIndex - 1)
+
+ cleanupChildren()
+ }
+
+ /**
+ * Removes a child node.
+ * If its a schema node and doesn't have any children anymore, its deleted.
+ */
+ fun removeChild(node: XMPNode) {
+
+ getOrCreateChildren().remove(node)
+
+ cleanupChildren()
+ }
+
+ /**
+ * Removes the children list if this node has no children anymore;
+ * checks if the provided node is a schema node and doesn't have any children anymore, its deleted.
+ */
+ private fun cleanupChildren() {
+
+ if (children?.isEmpty() == null)
+ children = null
+ }
+
+ /**
+ * Removes all children from the node.
+ */
+ fun removeChildren() {
+ children = null
+ }
+
+ fun getChildrenLength(): Int =
+ children?.size ?: 0
+
+ fun findChildByName(expr: String?): XMPNode? =
+ getOrCreateChildren()?.find { it.name == expr }
+
+ /**
+ * Returns the qualifier or empty list, if there are none.
+ * Will not lazily create the list!
+ */
+ fun getQualifier(): List =
+ qualifier ?: emptyList()
+
+ fun getQualifier(index: Int): XMPNode =
+ getOrCreateQualifier()[index - 1]
+
+ fun getQualifierLength(): Int =
+ qualifier?.size ?: 0
+
+ fun addQualifier(qualNode: XMPNode) {
+
+ assertQualifierNotExisting(qualNode.name!!)
+
+ qualNode.parent = this
+ qualNode.options.setQualifier(true)
+
+ options.setHasQualifiers(true)
+
+ // contraints
+ if (XMPConst.XML_LANG == qualNode.name) {
+
+ // "xml:lang" is always first and the option "hasLanguage" is set
+ options.setHasLanguage(true)
+
+ getOrCreateQualifier().add(0, qualNode)
+
+ } else if ("rdf:type" == qualNode.name) {
+
+ // "rdf:type" must be first or second after "xml:lang" and the option "hasType" is set
+ options.setHasType(true)
+
+ getOrCreateQualifier().add(
+ if (!options.hasLanguage()) 0 else 1,
+ qualNode
+ )
+
+ } else {
+
+ // other qualifiers are appended
+ getOrCreateQualifier().add(qualNode)
+ }
+ }
+
+ /**
+ * Removes one qualifier node and fixes the options.
+ */
+ fun removeQualifier(qualNode: XMPNode) {
+
+ if (XMPConst.XML_LANG == qualNode.name) {
+ // if "xml:lang" is removed, remove hasLanguage-flag too
+ options.setHasLanguage(false)
+ } else if ("rdf:type" == qualNode.name) {
+ // if "rdf:type" is removed, remove hasType-flag too
+ options.setHasType(false)
+ }
+
+ getOrCreateQualifier().remove(qualNode)
+
+ if (qualifier!!.isEmpty()) {
+ options.setHasQualifiers(false)
+ qualifier = null
+ }
+ }
+
+ /**
+ * Removes all qualifiers from the node and sets the options appropriate.
+ */
+ fun removeQualifiers() {
+
+ // clear qualifier related options
+ options.setHasQualifiers(false)
+ options.setHasLanguage(false)
+ options.setHasType(false)
+
+ qualifier = null
+ }
+
+ fun findQualifierByName(expr: String?): XMPNode? =
+ qualifier?.find { it.name == expr }
+
+ fun hasChildren(): Boolean =
+ children?.isNotEmpty() ?: false
+
+ fun iterateChildren(): Iterator =
+ children?.iterator() ?: emptySequence().iterator()
+
+ fun iterateChildrenMutable(): MutableIterator =
+ children?.listIterator() ?: mutableListOf().listIterator()
+
+ fun hasQualifier(): Boolean =
+ qualifier?.isNotEmpty() ?: false
+
+ fun iterateQualifier(): Iterator =
+ qualifier?.listIterator() ?: emptySequence().iterator()
+
+ override fun compareTo(other: XMPNode): Int {
+
+ return if (options.isSchemaNode())
+ value!!.compareTo(other.value!!)
+ else
+ name!!.compareTo(other.name!!)
+ }
+
+ /**
+ * Sorts the complete datamodel according to the following rules:
+ *
+ * * Nodes at one level are sorted by name, that is prefix + local name
+ * * Starting at the root node the children and qualifier are sorted recursively,
+ * which the following exceptions.
+ * * Sorting will not be used for arrays.
+ * * Within qualifier "xml:lang" and/or "rdf:type" stay at the top in that order, all others are sorted.
+ */
+ fun sort() {
+
+ // sort qualifier
+ if (hasQualifier()) {
+
+ val quals = getOrCreateQualifier().toTypedArray()
+
+ var sortFrom = 0
+
+ while (quals.size > sortFrom &&
+ (XMPConst.XML_LANG == quals[sortFrom].name || "rdf:type" == quals[sortFrom].name)
+ ) {
+ quals[sortFrom].sort()
+ sortFrom++
+ }
+
+ quals.sort(sortFrom, quals.size)
+
+ val iterator = qualifier!!.listIterator()
+
+ for (j in quals.indices) {
+ iterator.next()
+ iterator.set(quals[j])
+ quals[j].sort()
+ }
+ }
+
+ // sort children
+ if (hasChildren()) {
+
+ if (!options.isArray())
+ children!!.sort()
+
+ val it = iterateChildren()
+
+ while (it.hasNext())
+ it.next().sort()
+ }
+ }
+
+ // ------------------------------------------------------------------------------ private methods
+
+ private fun getOrCreateChildren(): MutableList {
+
+ if (children == null)
+ children = mutableListOf()
+
+ return children!!
+ }
+
+ private fun getOrCreateQualifier(): MutableList {
+
+ if (qualifier == null)
+ qualifier = mutableListOf()
+
+ return qualifier!!
+ }
+
+ /**
+ * Checks that a node name is not existing on the same level, except for array items.
+ */
+ private fun assertChildNotExisting(childName: String) {
+
+ if (XMPConst.ARRAY_ITEM_NAME != childName && findChildByName(childName) != null)
+ throw XMPException("Duplicate property or field node '$childName'", XMPError.BADXMP)
+ }
+
+ /**
+ * Checks that a qualifier name is not existing on the same level.
+ */
+ private fun assertQualifierNotExisting(qualifierName: String) {
+
+ if (XMPConst.ARRAY_ITEM_NAME != qualifierName && findQualifierByName(qualifierName) != null)
+ throw XMPException("Duplicate '$qualifierName' qualifier", XMPError.BADXMP)
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNodeUtils.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNodeUtils.kt
new file mode 100644
index 0000000..291f79a
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNodeUtils.kt
@@ -0,0 +1,712 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPMetaFactory.schemaRegistry
+import com.ashampoo.xmp.XMPUtils.encodeBase64
+import com.ashampoo.xmp.impl.Utils.normalizeLangValue
+import com.ashampoo.xmp.impl.Utils.replaceControlCharsWithSpace
+import com.ashampoo.xmp.impl.Utils.splitNameAndValue
+import com.ashampoo.xmp.impl.xpath.XMPPath
+import com.ashampoo.xmp.impl.xpath.XMPPathSegment
+import com.ashampoo.xmp.options.AliasOptions
+import com.ashampoo.xmp.options.PropertyOptions
+
+/**
+ * Utilities for `XMPNode`.
+ */
+object XMPNodeUtils {
+
+ const val CLT_NO_VALUES = 0
+
+ const val CLT_SPECIFIC_MATCH = 1
+
+ const val CLT_SINGLE_GENERIC = 2
+
+ const val CLT_MULTIPLE_GENERIC = 3
+
+ const val CLT_XDEFAULT = 4
+
+ const val CLT_FIRST_ITEM = 5
+
+ /**
+ * Find or create a schema node if `createNodes` is false and
+ *
+ * Note: If `createNodes` is `true`, it is **always** returned a valid node.
+ */
+ @kotlin.jvm.JvmStatic
+ fun findSchemaNode(tree: XMPNode, namespaceURI: String?, createNodes: Boolean): XMPNode? =
+ findSchemaNode(tree, namespaceURI, null, createNodes)
+
+ /**
+ * Find or create a schema node if `createNodes` is true.
+ *
+ * Note: If `createNodes` is `true`, it is **always** returned a valid node.
+ */
+ fun findSchemaNode(
+ tree: XMPNode,
+ namespaceURI: String?,
+ suggestedPrefix: String?,
+ createNodes: Boolean
+ ): XMPNode? {
+
+ // make sure that its the root
+ require(tree.parent == null)
+
+ var schemaNode = tree.findChildByName(namespaceURI)
+
+ if (schemaNode == null && createNodes) {
+
+ schemaNode = XMPNode(
+ name = namespaceURI,
+ value = null,
+ options = PropertyOptions().setSchemaNode(true)
+ )
+
+ schemaNode.isImplicit = true
+
+ // only previously registered schema namespaces are allowed in the XMP tree.
+ var prefix = schemaRegistry.getNamespacePrefix(namespaceURI!!)
+
+ if (prefix == null) {
+
+ prefix = if (suggestedPrefix != null && suggestedPrefix.length != 0)
+ schemaRegistry.registerNamespace(namespaceURI, suggestedPrefix)
+ else
+ throw XMPException("Unregistered schema namespace URI", XMPError.BADSCHEMA)
+ }
+
+ schemaNode.value = prefix
+
+ tree.addChild(schemaNode)
+ }
+
+ return schemaNode
+ }
+
+ /**
+ * Find or create a child node under a given parent node.
+ */
+ fun findChildNode(parent: XMPNode, childName: String?, createNodes: Boolean): XMPNode? {
+
+ if (!parent.options.isSchemaNode() && !parent.options.isStruct()) {
+
+ when {
+
+ !parent.isImplicit ->
+ throw XMPException(
+ "Named children only allowed for schemas and structs", XMPError.BADXPATH
+ )
+
+ parent.options.isArray() ->
+ throw XMPException("Named children not allowed for arrays", XMPError.BADXPATH)
+
+ createNodes ->
+ parent.options.setStruct(true)
+ }
+ }
+
+ var childNode = parent.findChildByName(childName)
+
+ if (childNode == null && createNodes) {
+
+ childNode = XMPNode(childName, null)
+
+ childNode.isImplicit = true
+
+ parent.addChild(childNode)
+ }
+
+ check(childNode != null || !createNodes)
+
+ return childNode
+ }
+
+ /**
+ * Follow an expanded path expression to find or create a node.
+ *
+ * @param xmpTree the node to begin the search.
+ * @param xpath the complete xpath
+ * @param createNodes flag if nodes shall be created (when called by `setProperty()`)
+ * @param leafOptions the options for the created leaf nodes (only when`createNodes == true`).
+ * @return Returns the node if found or created or `null`.
+
+ */
+ @kotlin.jvm.JvmStatic
+ fun findNode(
+ xmpTree: XMPNode,
+ xpath: XMPPath?,
+ createNodes: Boolean,
+ leafOptions: PropertyOptions?
+ ): XMPNode? {
+
+ if (xpath == null || xpath.size() == 0)
+ throw XMPException("Empty XMPPath", XMPError.BADXPATH)
+
+ // Root of implicitly created subtree to possible delete it later.
+ // Valid only if leaf is new.
+ var rootImplicitNode: XMPNode? = null
+
+ var currNode: XMPNode? =
+ findSchemaNode(xmpTree, xpath.getSegment(XMPPath.STEP_SCHEMA).name, createNodes)
+
+ if (currNode == null)
+ return null
+
+ if (currNode.isImplicit) {
+
+ currNode.isImplicit = false // Clear the implicit node bit.
+ rootImplicitNode = currNode // Save the top most implicit node.
+ }
+
+ // Now follow the remaining steps of the original XMPPath.
+ try {
+
+ for (index in 1 until xpath.size()) {
+
+ currNode = followXPathStep(currNode!!, xpath.getSegment(index), createNodes)
+
+ if (currNode == null) {
+
+ // delete implicitly created nodes
+ if (createNodes)
+ deleteNode(rootImplicitNode!!)
+
+ return null
+
+ } else if (currNode.isImplicit) {
+
+ // clear the implicit node flag
+ currNode.isImplicit = false
+
+ // if node is an ALIAS (can be only in root step, auto-create array
+ // when the path has been resolved from a not simple alias type
+ if (index == 1 &&
+ xpath.getSegment(index).isAlias && xpath.getSegment(index).aliasForm != 0
+ ) {
+ currNode.options.setOption(xpath.getSegment(index).aliasForm, true)
+ } else if ( // "CheckImplicitStruct" in C++
+ index < xpath.size() - 1 &&
+ xpath.getSegment(index).kind == XMPPath.STRUCT_FIELD_STEP &&
+ !currNode.options.isCompositeProperty()
+ ) {
+ currNode.options.setStruct(true)
+ }
+
+ if (rootImplicitNode == null)
+ rootImplicitNode = currNode // Save the top most implicit node.
+ }
+ }
+
+ } catch (ex: XMPException) {
+
+ // if new notes have been created prior to the error, delete them
+ if (rootImplicitNode != null)
+ deleteNode(rootImplicitNode)
+
+ throw ex
+ }
+
+ if (rootImplicitNode != null) {
+
+ // set options only if a node has been successful created
+ if (leafOptions != null)
+ currNode!!.options.mergeWith(leafOptions)
+
+ currNode!!.options = currNode.options
+ }
+
+ return currNode
+ }
+
+ /**
+ * Deletes the the given node and its children from its parent.
+ * Takes care about adjusting the flags.
+ *
+ * @param node the top-most node to delete.
+ */
+ @kotlin.jvm.JvmStatic
+ fun deleteNode(node: XMPNode) {
+
+ val parent = node.parent
+
+ if (node.options.isQualifier())
+ parent!!.removeQualifier(node)
+ else
+ parent!!.removeChild(node)
+
+ // delete empty Schema nodes
+ if (!parent.hasChildren() && parent.options.isSchemaNode())
+ parent.parent!!.removeChild(parent)
+ }
+
+ /**
+ * This is setting the value of a leaf node.
+ *
+ * @param node an XMPNode
+ * @param value a value
+ */
+ @kotlin.jvm.JvmStatic
+ fun setNodeValue(node: XMPNode, value: Any?) {
+
+ val strValue = serializeNodeValue(value)
+
+ if (!(node.options.isQualifier() && XMPConst.XML_LANG == node.name))
+ node.value = strValue
+ else
+ node.value = normalizeLangValue(strValue!!)
+ }
+
+ /**
+ * Verifies the PropertyOptions for consistancy and updates them as needed.
+ * If options are `null` they are created with default values.
+ *
+ * @param options the `PropertyOptions`
+ * @param itemValue the node value to set
+ * @return Returns the updated options.
+ *
+ */
+ @kotlin.jvm.JvmStatic
+ fun verifySetOptions(options: PropertyOptions, itemValue: Any?): PropertyOptions {
+
+ if (options.isArrayAltText())
+ options.setArrayAlternate(true)
+
+ if (options.isArrayAlternate())
+ options.setArrayOrdered(true)
+
+ if (options.isArrayOrdered())
+ options.setArray(true)
+
+ if (options.isCompositeProperty() && itemValue != null && itemValue.toString().length > 0)
+ throw XMPException("Structs and arrays can't have values", XMPError.BADOPTIONS)
+
+ options.assertConsistency(options.getOptions())
+
+ return options
+ }
+
+ /**
+ * Converts the node value to String, apply special conversions for defined
+ * types in XMP.
+ *
+ * @param value the node value to set
+ * @return Returns the String representation of the node value.
+ */
+ fun serializeNodeValue(value: Any?): String? {
+
+ if (value == null)
+ return null
+
+ val strValue: String = when (value) {
+ is Boolean -> if (value) XMPConst.TRUESTR else XMPConst.FALSESTR
+ is Int -> value.toString()
+ is Long -> value.toString()
+ is Double -> value.toString()
+ is ByteArray -> encodeBase64(value)
+ else -> value.toString()
+ }
+
+ return replaceControlCharsWithSpace(strValue)
+ }
+
+ /**
+ * After processing by ExpandXPath, a step can be of these forms:
+ *
+ * * qualName - A top level property or struct field.
+ * * [index] - An element of an array.
+ * * [last()] - The last element of an array.
+ * * [qualName="value"] - An element in an array of structs, chosen by a field value.
+ * * [?qualName="value"] - An element in an array, chosen by a qualifier value.
+ * * ?qualName - A general qualifier.
+ *
+ * Find the appropriate child node, resolving aliases, and optionally creating nodes.
+ */
+ private fun followXPathStep(
+ parentNode: XMPNode,
+ nextStep: XMPPathSegment,
+ createNodes: Boolean
+ ): XMPNode? {
+
+ var nextNode: XMPNode? = null
+ var index = 0
+ val stepKind = nextStep.kind
+
+ if (stepKind == XMPPath.STRUCT_FIELD_STEP) {
+ nextNode = findChildNode(parentNode, nextStep.name, createNodes)
+ } else if (stepKind == XMPPath.QUALIFIER_STEP) {
+ nextNode = findQualifierNode(parentNode, nextStep.name!!.substring(1), createNodes)
+ } else {
+
+ // This is an array indexing step. First get the index, then get the node.
+ if (!parentNode.options.isArray())
+ throw XMPException("Indexing applied to non-array", XMPError.BADXPATH)
+
+ index = when (stepKind) {
+
+ XMPPath.ARRAY_INDEX_STEP ->
+ findIndexedItem(parentNode, nextStep.name, createNodes)
+
+ XMPPath.ARRAY_LAST_STEP ->
+ parentNode.getChildrenLength()
+
+ XMPPath.FIELD_SELECTOR_STEP -> {
+
+ val result = splitNameAndValue(nextStep.name!!)
+ val fieldName = result[0]
+ val fieldValue = result[1]
+
+ lookupFieldSelector(parentNode, fieldName, fieldValue)
+ }
+
+ XMPPath.QUAL_SELECTOR_STEP -> {
+
+ val result = splitNameAndValue(nextStep.name!!)
+ val qualName = result[0]
+ val qualValue = result[1]
+
+ lookupQualSelector(parentNode, qualName, qualValue, nextStep.aliasForm)
+ }
+
+ else ->
+ throw XMPException(
+ "Unknown array indexing step in FollowXPathStep",
+ XMPError.INTERNALFAILURE
+ )
+ }
+
+ if (1 <= index && index <= parentNode.getChildrenLength())
+ nextNode = parentNode.getChild(index)
+ }
+
+ return nextNode
+ }
+
+ /**
+ * Find or create a qualifier node under a given parent node. Returns a pointer to the
+ * qualifier node, and optionally an iterator for the node's position in the parent's vector
+ * of qualifiers. The iterator is unchanged if no qualifier node (null) is returned.
+ *
+ * *Note:* On entry, the qualName parameter must not have the leading '?' from the XMPPath step.
+ */
+ private fun findQualifierNode(parent: XMPNode?, qualName: String, createNodes: Boolean): XMPNode? {
+
+ require(!qualName.startsWith("?"))
+
+ var qualNode = parent!!.findQualifierByName(qualName)
+
+ if (qualNode == null && createNodes) {
+ qualNode = XMPNode(qualName, null)
+ qualNode.isImplicit = true
+ parent.addQualifier(qualNode)
+ }
+
+ return qualNode
+ }
+
+ /**
+ * @param arrayNode an array node
+ * @param segment the segment containing the array index
+ * @param createNodes flag if new nodes are allowed to be created.
+ * @return Returns the index or index = -1 if not found
+ *
+ */
+ private fun findIndexedItem(arrayNode: XMPNode?, segment: String?, createNodes: Boolean): Int {
+
+ var segment = segment
+ var index: Int
+
+ try {
+
+ segment = segment!!.substring(1, segment.length - 1)
+ index = segment.toInt()
+
+ if (index < 1)
+ throw XMPException("Array index must be larger than zero", XMPError.BADXPATH)
+
+ } catch (ex: NumberFormatException) {
+ throw XMPException("Array index not digits.", XMPError.BADXPATH, ex)
+ }
+
+ if (createNodes && index == arrayNode!!.getChildrenLength() + 1) {
+
+ // Append a new last + 1 node.
+ val newItem = XMPNode(XMPConst.ARRAY_ITEM_NAME, null)
+
+ newItem.isImplicit = true
+
+ arrayNode.addChild(newItem)
+ }
+
+ return index
+ }
+
+ /**
+ * Searches for a field selector in a node:
+ * [fieldName="value] - an element in an array of structs, chosen by a field value.
+ * No implicit nodes are created by field selectors.
+ *
+ * @param arrayNode
+ * @param fieldName
+ * @param fieldValue
+ * @return Returns the index of the field if found, otherwise -1.
+ */
+ private fun lookupFieldSelector(arrayNode: XMPNode?, fieldName: String, fieldValue: String): Int {
+
+ var result = -1
+ var index = 1
+
+ while (index <= arrayNode!!.getChildrenLength() && result < 0) {
+
+ val currItem = arrayNode.getChild(index)
+
+ if (!currItem.options.isStruct())
+ throw XMPException("Field selector must be used on array of struct", XMPError.BADXPATH)
+
+ for (childIndex in 1..currItem.getChildrenLength()) {
+
+ val currField = currItem.getChild(childIndex)
+
+ if (fieldName != currField.name)
+ continue
+
+ if (fieldValue == currField.value) {
+ result = index
+ break
+ }
+ }
+
+ index++
+ }
+
+ return result
+ }
+
+ /**
+ * Searches for a qualifier selector in a node:
+ * [?qualName="value"] - an element in an array, chosen by a qualifier value.
+ * No implicit nodes are created for qualifier selectors, except for an alias to an x-default item.
+ */
+ private fun lookupQualSelector(
+ arrayNode: XMPNode,
+ qualName: String,
+ qualValue: String,
+ aliasForm: Int
+ ): Int {
+
+ return if (XMPConst.XML_LANG == qualName) {
+
+ val normalizedQualValue = normalizeLangValue(qualValue)
+
+ val index = lookupLanguageItem(arrayNode, normalizedQualValue)
+
+ if (index < 0 && aliasForm and AliasOptions.PROP_ARRAY_ALT_TEXT > 0) {
+
+ val langNode = XMPNode(XMPConst.ARRAY_ITEM_NAME, null)
+
+ val xdefault = XMPNode(XMPConst.XML_LANG, XMPConst.X_DEFAULT)
+
+ langNode.addQualifier(xdefault)
+
+ arrayNode.addChild(1, langNode)
+
+ 1
+
+ } else {
+ index
+ }
+
+ } else {
+
+ for (index in 1 until arrayNode.getChildrenLength()) {
+
+ val currItem = arrayNode.getChild(index)
+
+ for (qualifier in currItem.getQualifier())
+ if (qualName == qualifier.name && qualValue == qualifier.value)
+ return index
+ }
+
+ -1
+ }
+ }
+
+ /**
+ * Make sure the x-default item is first. Touch up "single value"
+ * arrays that have a default plus one real language. This case should have
+ * the same value for both items. Older Adobe apps were hardwired to only
+ * use the "x-default" item, so we copy that value to the other
+ * item.
+ *
+ * @param arrayNode an alt text array node
+ */
+ fun normalizeLangArray(arrayNode: XMPNode) {
+
+ if (!arrayNode.options.isArrayAltText())
+ return
+
+ // check if node with x-default qual is first place
+ for (index in 2..arrayNode.getChildrenLength()) {
+
+ val child = arrayNode.getChild(index)
+
+ if (child.hasQualifier() && XMPConst.X_DEFAULT == child.getQualifier(1).value) {
+
+ // move node to first place
+ arrayNode.removeChild(index)
+ arrayNode.addChild(1, child)
+
+ if (index == 2)
+ arrayNode.getChild(2).value = child.value
+
+ break
+ }
+ }
+ }
+
+ /**
+ * See if an array is an alt-text array. If so, make sure the x-default item
+ * is first.
+ *
+ * @param arrayNode the array node to check if its an alt-text array
+ */
+ fun detectAltText(arrayNode: XMPNode) {
+
+ if (arrayNode.options.isArrayAlternate() && arrayNode.hasChildren()) {
+
+ var isAltText = false
+
+ for (child in arrayNode.getChildren()) {
+ if (child.options.hasLanguage()) {
+ isAltText = true
+ break
+ }
+ }
+
+ if (isAltText) {
+ arrayNode.options.setArrayAltText(true)
+ normalizeLangArray(arrayNode)
+ }
+ }
+ }
+
+ /**
+ * Appends a language item to an alt text array.
+ */
+ @kotlin.jvm.JvmStatic
+ fun appendLangItem(arrayNode: XMPNode, itemLang: String?, itemValue: String?) {
+
+ val newItem = XMPNode(XMPConst.ARRAY_ITEM_NAME, itemValue)
+ val langQual = XMPNode(XMPConst.XML_LANG, itemLang)
+
+ newItem.addQualifier(langQual)
+
+ if (XMPConst.X_DEFAULT != langQual.value)
+ arrayNode.addChild(newItem)
+ else
+ arrayNode.addChild(1, newItem)
+ }
+
+ /**
+ * 1. Look for an exact match with the specific language.
+ * 1. If a generic language is given, look for partial matches.
+ * 1. Look for an "x-default"-item.
+ * 1. Choose the first item.
+ */
+ @kotlin.jvm.JvmStatic
+ fun chooseLocalizedText(arrayNode: XMPNode, genericLang: String?, specificLang: String): Array {
+
+ // See if the array has the right form. Allow empty alt arrays, that is what parsing returns.
+
+ if (!arrayNode.options.isArrayAltText())
+ throw XMPException("Localized text array is not alt-text", XMPError.BADXPATH)
+ else if (!arrayNode.hasChildren())
+ return arrayOf(CLT_NO_VALUES, null)
+
+ var foundGenericMatches = 0
+ var resultNode: XMPNode? = null
+ var xDefault: XMPNode? = null
+
+ // Look for the first partial match with the generic language.
+ val it = arrayNode.iterateChildren()
+
+ while (it.hasNext()) {
+
+ val currItem = it.next()
+
+ // perform some checks on the current item
+ if (currItem.options.isCompositeProperty())
+ throw XMPException("Alt-text array item is not simple", XMPError.BADXPATH)
+ else if (!currItem.hasQualifier() || XMPConst.XML_LANG != currItem.getQualifier(1).name)
+ throw XMPException("Alt-text array item has no language qualifier", XMPError.BADXPATH)
+
+ val currLang = currItem.getQualifier(1).value
+
+ // Look for an exact match with the specific language.
+ when {
+
+ specificLang == currLang ->
+ return arrayOf(CLT_SPECIFIC_MATCH, currItem)
+
+ genericLang != null && currLang!!.startsWith(genericLang) -> {
+
+ if (resultNode == null)
+ resultNode = currItem
+
+ // ! Don't return/break, need to look for other matches.
+ foundGenericMatches++
+ }
+
+ XMPConst.X_DEFAULT == currLang ->
+ xDefault = currItem
+ }
+ }
+
+ // evaluate loop
+ return when {
+
+ foundGenericMatches == 1 ->
+ arrayOf(CLT_SINGLE_GENERIC, resultNode)
+
+ foundGenericMatches > 1 ->
+ arrayOf(CLT_MULTIPLE_GENERIC, resultNode)
+
+ xDefault != null ->
+ arrayOf(CLT_XDEFAULT, xDefault)
+
+ else -> // Everything failed, choose the first item.
+ arrayOf(CLT_FIRST_ITEM, arrayNode.getChild(1))
+ }
+ }
+
+ /**
+ * Looks for the appropriate language item in a text alternative array.item
+ * Returns the index if the language has been found, -1 otherwise.
+ */
+ fun lookupLanguageItem(arrayNode: XMPNode?, language: String): Int {
+
+ if (!arrayNode!!.options.isArray())
+ throw XMPException("Language item must be used on array", XMPError.BADXPATH)
+
+ for (index in 1..arrayNode.getChildrenLength()) {
+
+ val child = arrayNode.getChild(index)
+
+ if (!child.hasQualifier() || XMPConst.XML_LANG != child.getQualifier(1).name)
+ continue
+ else if (language == child.getQualifier(1).value)
+ return index
+ }
+
+ return -1
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNormalizer.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNormalizer.kt
new file mode 100644
index 0000000..39857ad
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPNormalizer.kt
@@ -0,0 +1,496 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPMeta
+import com.ashampoo.xmp.XMPMetaFactory.schemaRegistry
+import com.ashampoo.xmp.impl.Utils.checkUUIDFormat
+import com.ashampoo.xmp.impl.xpath.XMPPathParser.expandXPath
+import com.ashampoo.xmp.options.ParseOptions
+import com.ashampoo.xmp.options.PropertyOptions
+
+internal object XMPNormalizer {
+
+ /**
+ * caches the correct dc-property array forms
+ */
+ private val dcArrayForms: Map = createDCArrays()
+
+ /**
+ * Normalizes a raw parsed XMPMeta-Object
+ *
+ * @param xmp the raw metadata object
+ * @param options the parsing options
+ * @return Returns the normalized metadata object
+ *
+ */
+ @kotlin.jvm.JvmStatic
+ fun normalize(xmp: XMPMetaImpl, options: ParseOptions): XMPMeta {
+
+ val tree = xmp.root
+
+ touchUpDataModel(xmp)
+ moveExplicitAliases(tree, options)
+ tweakOldXMP(tree)
+ deleteEmptySchemas(tree)
+
+ return xmp
+ }
+
+ /**
+ * Tweak old XMP: Move an instance ID from rdf:about to the
+ * *xmpMM:InstanceID* property. An old instance ID usually looks
+ * like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign
+ * 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4".
+ *
+ * If the name looks like a UUID simply move it to *xmpMM:InstanceID*,
+ * don't worry about any existing *xmpMM:InstanceID*. Both will
+ * only be present when a newer file with the *xmpMM:InstanceID*
+ * property is updated by an old app that uses *rdf:about*.
+ */
+ private fun tweakOldXMP(tree: XMPNode) {
+
+ if (tree.name != null && tree.name!!.length >= Utils.UUID_LENGTH) {
+
+ var nameStr = tree.name!!.lowercase()
+
+ if (nameStr.startsWith("uuid:"))
+ nameStr = nameStr.substring(5)
+
+ if (checkUUIDFormat(nameStr)) {
+
+ // move UUID to xmpMM:InstanceID and remove it from the root node
+ val path = expandXPath(XMPConst.NS_XMP_MM, "InstanceID")
+ val idNode = XMPNodeUtils.findNode(tree, path, true, null)
+
+ if (idNode != null) {
+
+ idNode.options = PropertyOptions() // Clobber any existing xmpMM:InstanceID.
+ idNode.value = "uuid:$nameStr"
+ idNode.removeChildren()
+ idNode.removeQualifiers()
+
+ tree.name = null
+
+ } else
+ throw XMPException("Failure creating xmpMM:InstanceID", XMPError.INTERNALFAILURE)
+ }
+ }
+ }
+
+ /**
+ * Visit all schemas to do general fixes and handle special cases.
+ */
+ private fun touchUpDataModel(xmp: XMPMetaImpl) {
+
+ // make sure the DC schema is existing, because it might be needed within the normalization
+ // if not touched it will be removed by removeEmptySchemas
+ XMPNodeUtils.findSchemaNode(xmp.root, XMPConst.NS_DC, true)
+
+ // Do the special case fixes within each schema.
+ val it = xmp.root.iterateChildren()
+
+ while (it.hasNext()) {
+
+ val currSchema = it.next()
+
+ when {
+
+ XMPConst.NS_DC == currSchema.name ->
+ normalizeDCArrays(currSchema)
+
+ XMPConst.NS_EXIF == currSchema.name ->
+ XMPNodeUtils.findChildNode(currSchema, "exif:UserComment", false)
+ ?.let { userComment -> repairAltText(userComment) }
+
+ XMPConst.NS_XMP_RIGHTS == currSchema.name ->
+ XMPNodeUtils.findChildNode(currSchema, "xmpRights:UsageTerms", false)
+ ?.let { usageTerms -> repairAltText(usageTerms) }
+ }
+ }
+ }
+
+ /**
+ * Undo the denormalization performed by the XMP used in Acrobat 5.
+ * If a Dublin Core array had only one item, it was serialized as a simple property.
+ * The `xml:lang` attribute was dropped from an `alt-text` item if the language was `x-default`.
+ *
+ */
+ private fun normalizeDCArrays(dcSchema: XMPNode) {
+
+ for (index in 1..dcSchema.getChildrenLength()) {
+
+ val currProp = dcSchema.getChild(index)
+
+ val arrayForm = dcArrayForms[currProp.name]
+
+ if (arrayForm == null) {
+
+ continue
+
+ } else if (currProp.options.isSimple()) {
+
+ // create a new array and add the current property as child, if it was formerly simple
+ val newArray = XMPNode(currProp.name, null, arrayForm)
+
+ currProp.name = XMPConst.ARRAY_ITEM_NAME
+ newArray.addChild(currProp)
+ dcSchema.replaceChild(index, newArray)
+
+ // fix language alternatives
+ if (arrayForm.isArrayAltText() && !currProp.options.hasLanguage()) {
+
+ val newLang = XMPNode(XMPConst.XML_LANG, XMPConst.X_DEFAULT)
+
+ currProp.addQualifier(newLang)
+ }
+
+ } else {
+
+ // clear array options and add corrected array form if it has been an array before
+ currProp.options.setOption(
+ PropertyOptions.ARRAY or
+ PropertyOptions.ARRAY_ORDERED or
+ PropertyOptions.ARRAY_ALTERNATE or
+ PropertyOptions.ARRAY_ALT_TEXT,
+ false
+ )
+
+ currProp.options.mergeWith(arrayForm)
+
+ // applying for "dc:description", "dc:rights", "dc:title"
+ if (arrayForm.isArrayAltText())
+ repairAltText(currProp)
+ }
+ }
+ }
+
+ /**
+ * Make sure that the array is well-formed AltText. Each item must be simple
+ * and have an "xml:lang" qualifier. If repairs are needed, keep simple
+ * non-empty items by adding the "xml:lang" with value "x-repair".
+ *
+ * @param arrayNode the property node of the array to repair.
+ */
+ private fun repairAltText(arrayNode: XMPNode?) {
+
+ if (arrayNode == null || !arrayNode.options.isArray())
+ return // Already OK or not even an array.
+
+ // fix options
+ arrayNode.options.setArrayOrdered(true).setArrayAlternate(true).setArrayAltText(true)
+
+ val it = arrayNode.iterateChildrenMutable()
+
+ while (it.hasNext()) {
+
+ val currChild = it.next()
+
+ if (currChild.options.isCompositeProperty()) {
+
+ // Delete non-simple children.
+ it.remove()
+
+ } else if (!currChild.options.hasLanguage()) {
+
+ val childValue = currChild.value
+
+ if (childValue == null || childValue.length == 0) {
+
+ // Delete empty valued children that have no xml:lang.
+ it.remove()
+
+ } else {
+
+ // Add an xml:lang qualifier with the value "x-repair".
+ val repairLang = XMPNode(XMPConst.XML_LANG, "x-repair")
+ currChild.addQualifier(repairLang)
+ }
+ }
+ }
+ }
+
+ /**
+ * Visit all of the top level nodes looking for aliases. If there is
+ * no base, transplant the alias subtree. If there is a base and strict
+ * aliasing is on, make sure the alias and base subtrees match.
+ *
+ * @param tree the root of the metadata tree
+ * @param options th parsing options
+ */
+ private fun moveExplicitAliases(tree: XMPNode, options: ParseOptions) {
+
+ if (!tree.hasAliases)
+ return
+
+ tree.hasAliases = false
+
+ val strictAliasing = options.getStrictAliasing()
+
+ val schemaIt: Iterator = tree.iterateChildren()
+
+ while (schemaIt.hasNext()) {
+
+ val currSchema = schemaIt.next()
+
+ if (!currSchema.hasAliases)
+ continue
+
+ val propertyIt = currSchema.iterateChildrenMutable()
+
+ while (propertyIt.hasNext()) {
+
+ val currProp = propertyIt.next()
+
+ if (!currProp.isAlias)
+ continue
+
+ currProp.isAlias = false
+
+ // Find the base path, look for the base schema and root node.
+ val info = schemaRegistry.findAlias(currProp.name!!)
+
+ if (info != null) {
+
+ // find or create schema
+ val baseSchema = XMPNodeUtils.findSchemaNode(
+ tree, info.getNamespace(), null, true
+ )
+
+ checkNotNull(baseSchema) { "SchemaNode should have been created." }
+
+ baseSchema.isImplicit = false
+
+ var baseNode = XMPNodeUtils.findChildNode(
+ baseSchema,
+ info.getPrefix() + info.getPropName(), false
+ )
+
+ if (baseNode == null) {
+
+ if (info.getAliasForm().isSimple()) {
+
+ // A top-to-top alias, transplant the property.
+ // change the alias property name to the base name
+ val qname = info.getPrefix() + info.getPropName()
+
+ currProp.name = qname
+
+ baseSchema.addChild(currProp)
+
+ // remove the alias property
+ propertyIt.remove()
+
+ } else {
+
+ // An alias to an array item,
+ // create the array and transplant the property.
+ baseNode = XMPNode(
+ name = info.getPrefix() + info.getPropName(),
+ value = null,
+ options = info.getAliasForm().toPropertyOptions()
+ )
+
+ baseSchema.addChild(baseNode)
+
+ transplantArrayItemAlias(propertyIt, currProp, baseNode)
+ }
+
+ } else if (info.getAliasForm().isSimple()) {
+
+ // The base node does exist and this is a top-to-top alias.
+ // Check for conflicts if strict aliasing is on.
+ // Remove and delete the alias subtree.
+ if (strictAliasing)
+ compareAliasedSubtrees(currProp, baseNode, true)
+
+ propertyIt.remove()
+
+ } else {
+
+ // This is an alias to an array item and the array exists.
+ // Look for the aliased item.
+ // Then transplant or check & delete as appropriate.
+ var itemNode: XMPNode? = null
+
+ if (info.getAliasForm().isArrayAltText()) {
+
+ val xdIndex = XMPNodeUtils.lookupLanguageItem(baseNode, XMPConst.X_DEFAULT)
+
+ if (xdIndex != -1)
+ itemNode = baseNode.getChild(xdIndex)
+
+ } else if (baseNode.hasChildren()) {
+
+ itemNode = baseNode.getChild(1)
+ }
+
+ if (itemNode == null) {
+
+ transplantArrayItemAlias(propertyIt, currProp, baseNode)
+
+ } else {
+
+ if (strictAliasing)
+ compareAliasedSubtrees(currProp, itemNode, true)
+
+ propertyIt.remove()
+ }
+ }
+ }
+ }
+
+ currSchema.hasAliases = false
+ }
+ }
+
+ /**
+ * Moves an alias node of array form to another schema into an array
+ *
+ * @param propertyIt the property iterator of the old schema (used to delete the property)
+ * @param childNode the node to be moved
+ * @param baseArray the base array for the array item
+ *
+ */
+ private fun transplantArrayItemAlias(
+ propertyIt: MutableIterator,
+ childNode: XMPNode,
+ baseArray: XMPNode
+ ) {
+
+ if (baseArray.options.isArrayAltText()) {
+
+ // *** Allow x-default.
+ if (childNode.options.hasLanguage())
+ throw XMPException("Alias to x-default already has a language qualifier", XMPError.BADXMP)
+
+ val langQual = XMPNode(XMPConst.XML_LANG, XMPConst.X_DEFAULT)
+
+ childNode.addQualifier(langQual)
+ }
+
+ propertyIt.remove()
+
+ childNode.name = XMPConst.ARRAY_ITEM_NAME
+
+ baseArray.addChild(childNode)
+ }
+
+ /**
+ * Remove all empty schemas from the metadata tree that were generated during the rdf parsing.
+ *
+ * @param tree the root of the metadata tree
+ */
+ private fun deleteEmptySchemas(tree: XMPNode) {
+
+ // Delete empty schema nodes. Do this last, other cleanup can make empty schema.
+
+ val it = tree.iterateChildrenMutable()
+
+ while (it.hasNext()) {
+
+ val schema = it.next()
+
+ if (!schema.hasChildren())
+ it.remove()
+ }
+ }
+
+ /**
+ * The outermost call is special. The names almost certainly differ. The
+ * qualifiers (and hence options) will differ for an alias to the x-default
+ * item of a langAlt array.
+ *
+ * @param aliasNode the alias node
+ * @param baseNode the base node of the alias
+ * @param outerCall marks the outer call of the recursion
+ *
+ */
+ private fun compareAliasedSubtrees(
+ aliasNode: XMPNode,
+ baseNode: XMPNode,
+ outerCall: Boolean
+ ) {
+
+ if (aliasNode.value != baseNode.value || aliasNode.getChildrenLength() != baseNode.getChildrenLength())
+ throw XMPException("Mismatch between alias and base nodes", XMPError.BADXMP)
+
+ if (!outerCall &&
+ (
+ aliasNode.name != baseNode.name ||
+ !aliasNode.options.equals(baseNode.options) ||
+ aliasNode.getQualifierLength() != baseNode.getQualifierLength()
+ )
+ )
+ throw XMPException("Mismatch between alias and base nodes", XMPError.BADXMP)
+
+ run {
+ val an = aliasNode.iterateChildren()
+ val bn = baseNode.iterateChildren()
+
+ while (an.hasNext() && bn.hasNext()) {
+ val aliasChild = an.next()
+ val baseChild = bn.next()
+ compareAliasedSubtrees(aliasChild, baseChild, false)
+ }
+ }
+
+ val an = aliasNode.iterateQualifier()
+ val bn = baseNode.iterateQualifier()
+
+ while (an.hasNext() && bn.hasNext()) {
+
+ val aliasQual = an.next()
+ val baseQual = bn.next()
+
+ compareAliasedSubtrees(aliasQual, baseQual, false)
+ }
+ }
+
+ /**
+ * Initializes the map that contains the known arrays, that are fixed by
+ * [XMPNormalizer.normalizeDCArrays].
+ */
+ private fun createDCArrays(): Map {
+
+ val dcArrayForms = mutableMapOf()
+
+ // Properties supposed to be a "Bag".
+ val bagForm = PropertyOptions()
+ bagForm.setArray(true)
+ dcArrayForms["dc:contributor"] = bagForm
+ dcArrayForms["dc:language"] = bagForm
+ dcArrayForms["dc:publisher"] = bagForm
+ dcArrayForms["dc:relation"] = bagForm
+ dcArrayForms["dc:subject"] = bagForm
+ dcArrayForms["dc:type"] = bagForm
+
+ // Properties supposed to be a "Seq".
+ val seqForm = PropertyOptions()
+ seqForm.setArray(true)
+ seqForm.setArrayOrdered(true)
+ dcArrayForms["dc:creator"] = seqForm
+ dcArrayForms["dc:date"] = seqForm
+
+ // Properties supposed to be an "Alt" in alternative-text form.
+ val altTextForm = PropertyOptions()
+ altTextForm.setArray(true)
+ altTextForm.setArrayOrdered(true)
+ altTextForm.setArrayAlternate(true)
+ altTextForm.setArrayAltText(true)
+ dcArrayForms["dc:description"] = altTextForm
+ dcArrayForms["dc:rights"] = altTextForm
+ dcArrayForms["dc:title"] = altTextForm
+
+ return dcArrayForms
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPRDFParser.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPRDFParser.kt
new file mode 100644
index 0000000..971e819
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPRDFParser.kt
@@ -0,0 +1,1185 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPMetaFactory.schemaRegistry
+import com.ashampoo.xmp.options.ParseOptions
+import com.ashampoo.xmp.options.PropertyOptions
+import nl.adaptivity.xmlutil.dom.Attr
+import nl.adaptivity.xmlutil.dom.Element
+import nl.adaptivity.xmlutil.dom.Node
+import nl.adaptivity.xmlutil.dom.Text
+import nl.adaptivity.xmlutil.dom.attributes
+import nl.adaptivity.xmlutil.dom.childNodes
+import nl.adaptivity.xmlutil.dom.data
+import nl.adaptivity.xmlutil.dom.length
+import nl.adaptivity.xmlutil.dom.localName
+import nl.adaptivity.xmlutil.dom.namespaceURI
+import nl.adaptivity.xmlutil.dom.nodeName
+import nl.adaptivity.xmlutil.dom.nodeType
+import nl.adaptivity.xmlutil.dom.ownerElement
+import nl.adaptivity.xmlutil.dom.prefix
+import nl.adaptivity.xmlutil.dom.value
+
+/**
+ * Parser for "normal" XML serialisation of RDF.
+ */
+internal object XMPRDFParser : XMPError {
+
+ const val RDFTERM_OTHER = 0
+
+ /**
+ * Start of coreSyntaxTerms.
+ */
+ const val RDFTERM_RDF = 1
+
+ const val RDFTERM_ID = 2
+
+ const val RDFTERM_ABOUT = 3
+
+ const val RDFTERM_PARSE_TYPE = 4
+
+ const val RDFTERM_RESOURCE = 5
+
+ const val RDFTERM_NODE_ID = 6
+
+ /**
+ * End of coreSyntaxTerms
+ */
+ const val RDFTERM_DATATYPE = 7
+
+ /**
+ * Start of additions for syntax Terms.
+ */
+ const val RDFTERM_DESCRIPTION = 8
+
+ /**
+ * End of of additions for syntaxTerms.
+ */
+ const val RDFTERM_LI = 9
+
+ /**
+ * Start of oldTerms.
+ */
+ const val RDFTERM_ABOUT_EACH = 10
+
+ const val RDFTERM_ABOUT_EACH_PREFIX = 11
+
+ /**
+ * End of oldTerms.
+ */
+ const val RDFTERM_BAG_ID = 12
+
+ const val RDFTERM_FIRST_CORE = RDFTERM_RDF
+
+ const val RDFTERM_LAST_CORE = RDFTERM_DATATYPE
+
+ /**
+ * ! Yes, the syntax terms include the core terms.
+ */
+ const val RDFTERM_FIRST_SYNTAX = RDFTERM_FIRST_CORE
+
+ const val RDFTERM_LAST_SYNTAX = RDFTERM_LI
+
+ const val RDFTERM_FIRST_OLD = RDFTERM_ABOUT_EACH
+
+ const val RDFTERM_LAST_OLD = RDFTERM_BAG_ID
+
+ /**
+ * this prefix is used for default namespaces
+ */
+ const val DEFAULT_PREFIX = "_dflt"
+
+ /**
+ * The main parsing method. The XML tree is walked through from the root node and and XMP tree
+ * is created. This is a raw parse, the normalisation of the XMP tree happens outside.
+ *
+ */
+ @kotlin.jvm.JvmStatic
+ fun parse(xmlRoot: Node, options: ParseOptions): XMPMetaImpl {
+
+ val xmp = XMPMetaImpl()
+
+ parseRdfRoot(xmp, xmlRoot, options)
+
+ return xmp
+ }
+
+ /**
+ * Each of these parsing methods is responsible for recognizing an RDF
+ * syntax production and adding the appropriate structure to the XMP tree.
+ * They simply return for success, failures will throw an exception.
+ */
+ @Suppress("ThrowsCount")
+ fun parseRdfRoot(xmp: XMPMetaImpl, rdfRdfNode: Node, options: ParseOptions) {
+
+ if (rdfRdfNode.nodeName != "rdf:RDF")
+ throw XMPException("Root node should be of type rdf:RDF", XMPError.BADRDF)
+
+ if (rdfRdfNode !is Element)
+ throw XMPException("Root node must be of element type.", XMPError.BADRDF)
+
+ if (rdfRdfNode.attributes.length == 0)
+ throw XMPException("Illegal: rdf:RDF node has no attributes", XMPError.BADRDF)
+
+ for (index in 0 until rdfRdfNode.childNodes.length) {
+
+ val child = rdfRdfNode.childNodes.item(index)!!
+
+ /* Filter whitespace nodes. */
+ if (isWhitespaceNode(child))
+ continue
+
+ parseRdfNodeElement(xmp, xmp.root, child as Element, true, options)
+ }
+ }
+
+ /**
+ * 7.2.5 nodeElementURIs
+ * anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
+ *
+ * 7.2.11 nodeElement
+ * start-element ( URI == nodeElementURIs,
+ * attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
+ * propertyEltList
+ * end-element()
+ *
+ * A node element URI is rdf:Description or anything else that is not an RDF
+ * term.
+ */
+ private fun parseRdfNodeElement(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Element,
+ isTopLevel: Boolean,
+ options: ParseOptions
+ ) {
+
+ val nodeTerm = getRDFTermKind(xmlNode)
+
+ if (nodeTerm != RDFTERM_DESCRIPTION && nodeTerm != RDFTERM_OTHER)
+ throw XMPException("Node element must be rdf:Description or typed node", XMPError.BADRDF)
+
+ if (isTopLevel && nodeTerm == RDFTERM_OTHER)
+ throw XMPException("Top level typed node not allowed", XMPError.BADXMP)
+
+ parseRdfNodeElementAttrs(xmp, xmpParent, xmlNode, isTopLevel)
+ parseRdfPropertyElementList(xmp, xmpParent, xmlNode, isTopLevel, options)
+ }
+
+ /**
+ * 7.2.7 propertyAttributeURIs
+ * anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
+ *
+ * 7.2.11 nodeElement
+ * start-element ( URI == nodeElementURIs,
+ * attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
+ * propertyEltList
+ *
+ * Process the attribute list for an RDF node element. A property attribute URI is
+ * anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored,
+ * as are rdf:about attributes on inner nodes.
+ *
+ */
+ private fun parseRdfNodeElementAttrs(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Element,
+ isTopLevel: Boolean
+ ) {
+
+ // Used to detect attributes that are mutually exclusive.
+ var exclusiveAttrs = 0
+
+ for (index in 0 until xmlNode.attributes.length) {
+
+ val attribute = xmlNode.attributes.item(index) as Attr
+
+ // quick hack, ns declarations do not appear in C++
+ // ignore "ID" without namespace
+ if ("xmlns" == attribute.prefix || attribute.prefix == null && "xmlns" == attribute.nodeName)
+ continue
+
+ val attrTerm = getRDFTermKind(attribute)
+
+ when (attrTerm) {
+
+ RDFTERM_ID, RDFTERM_NODE_ID, RDFTERM_ABOUT -> {
+
+ if (exclusiveAttrs > 0)
+ throw XMPException("Mutally exclusive about, ID, nodeID attributes", XMPError.BADRDF)
+
+ exclusiveAttrs++
+
+ if (isTopLevel && attrTerm == RDFTERM_ABOUT) {
+
+ // This is the rdf:about attribute on a top level node. Set
+ // the XMP tree name if
+ // it doesn't have a name yet. Make sure this name matches
+ // the XMP tree name.
+ if (xmpParent.name != null && xmpParent.name!!.length > 0) {
+
+ if (xmpParent.name != attribute.value)
+ throw XMPException("Mismatched top level rdf:about values", XMPError.BADXMP)
+
+ } else {
+ xmpParent.name = attribute.value
+ }
+ }
+ }
+
+ RDFTERM_OTHER ->
+ addChildNode(xmp, xmpParent, attribute, attribute.value, isTopLevel)
+
+ else -> throw XMPException("Invalid nodeElement attribute", XMPError.BADRDF)
+ }
+ }
+ }
+
+ /**
+ * 7.2.13 propertyEltList
+ * ws* ( propertyElt ws* )*
+ *
+ * @param xmp the xmp metadata object that is generated
+ * @param xmpParent the parent xmp node
+ * @param xmlParent the currently processed XML node
+ * @param isTopLevel Flag if the node is a top-level node
+ * @param options ParseOptions to indicate the parse options provided by the client
+ *
+ */
+ private fun parseRdfPropertyElementList(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlParent: Node?,
+ isTopLevel: Boolean,
+ options: ParseOptions
+ ) {
+
+ for (index in 0 until xmlParent!!.childNodes.length) {
+
+ val currChild = xmlParent.childNodes.item(index)!!
+
+ if (isWhitespaceNode(currChild))
+ continue
+
+ if (currChild !is Element)
+ throw XMPException("Expected property element node not found", XMPError.BADRDF)
+
+ parseRdfPropertyElement(xmp, xmpParent, currChild, isTopLevel, options)
+ }
+ }
+
+ /**
+ * 7.2.14 propertyElt
+ *
+ * resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
+ * parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt |
+ * parseTypeOtherPropertyElt | emptyPropertyElt
+ *
+ * 7.2.15 resourcePropertyElt
+ * start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
+ * ws* nodeElement ws*
+ * end-element()
+ *
+ * 7.2.16 literalPropertyElt
+ * start-element (
+ * URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
+ * text()
+ * end-element()
+ *
+ * 7.2.17 parseTypeLiteralPropertyElt
+ * start-element (
+ * URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
+ * literal
+ * end-element()
+ *
+ * 7.2.18 parseTypeResourcePropertyElt
+ * start-element (
+ * URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
+ * propertyEltList
+ * end-element()
+ *
+ * 7.2.19 parseTypeCollectionPropertyElt
+ * start-element (
+ * URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
+ * nodeElementList
+ * end-element()
+ *
+ * 7.2.20 parseTypeOtherPropertyElt
+ * start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
+ * propertyEltList
+ * end-element()
+ *
+ * 7.2.21 emptyPropertyElt
+ * start-element ( URI == propertyElementURIs,
+ * attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
+ * end-element()
+ *
+ * The various property element forms are not distinguished by the XML element name,
+ * but by their attributes for the most part. The exceptions are resourcePropertyElt and
+ * literalPropertyElt. They are distinguished by their XML element content.
+ *
+ * NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can
+ * appear in many of these. We have to allow for it in the attibute counts below.
+ *
+ */
+ private fun parseRdfPropertyElement(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Element,
+ isTopLevel: Boolean,
+ options: ParseOptions
+ ) {
+
+ val nodeTerm = getRDFTermKind(xmlNode)
+
+ if (!isPropertyElementName(nodeTerm))
+ throw XMPException("Invalid property element name", XMPError.BADRDF)
+
+ // remove the namespace-definitions from the list
+ val attributes = xmlNode.attributes
+
+ var nsAttrs: MutableList? = null
+
+ for (index in 0 until attributes.length) {
+
+ val attribute = attributes.item(index) as Attr
+
+ if ("xmlns" == attribute.prefix || attribute.prefix == null && "xmlns" == attribute.nodeName) {
+
+ if (nsAttrs == null)
+ nsAttrs = mutableListOf()
+
+ nsAttrs.add(attribute.nodeName)
+ }
+ }
+
+ if (nsAttrs != null) {
+
+ val it = nsAttrs.iterator()
+
+ while (it.hasNext())
+ attributes.removeNamedItem(it.next())
+ }
+
+ if (attributes.length > 3) {
+
+ // Only an emptyPropertyElt can have more than 3 attributes.
+ parseEmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel)
+
+ } else {
+
+ // Look through the attributes for one that isn't rdf:ID or xml:lang,
+ // it will usually tell what we should be dealing with.
+ // The called routines must verify their specific syntax!
+ for (index in 0 until attributes.length) {
+
+ val attribute = attributes.item(index) as Attr
+
+ val attrValue = attribute.value
+
+ val condition = XMPConst.XML_LANG == attribute.nodeName &&
+ !("ID" == attribute.localName && XMPConst.NS_RDF == attribute.namespaceURI)
+
+ if (!condition) {
+
+ when {
+
+ "datatype" == attribute.localName && XMPConst.NS_RDF == attribute.namespaceURI ->
+ parseRdfLiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel)
+
+ !("parseType" == attribute.localName && XMPConst.NS_RDF == attribute.namespaceURI) ->
+ parseEmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel)
+
+ "Literal" == attrValue ->
+ throw XMPException("Literal property element not allowed", XMPError.BADXMP)
+
+ "Resource" == attrValue ->
+ parseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel, options)
+
+ "Collection" == attrValue ->
+ throw XMPException("Collection property element forbidden", XMPError.BADXMP)
+
+ else ->
+ throw XMPException("Other property element not allowed", XMPError.BADXMP)
+ }
+
+ return
+ }
+ }
+
+ // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt,
+ // or an emptyPropertyElt. Look at the child XML nodes to decide which.
+ if (xmlNode.childNodes.length > 0) {
+
+ for (index in 0 until xmlNode.childNodes.length) {
+
+ val currentChild = xmlNode.childNodes.item(index)
+
+ if (currentChild !is Text) {
+
+ parseRdfResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel, options)
+
+ return
+ }
+ }
+
+ parseRdfLiteralPropertyElement(xmp, xmpParent, xmlNode, isTopLevel)
+
+ } else
+ parseEmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel)
+ }
+ }
+
+ /**
+ * 7.2.15 resourcePropertyElt
+ * start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
+ * ws* nodeElement ws*
+ * end-element()
+ *
+ * This handles structs using an rdf:Description node,
+ * arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified
+ * properties written with rdf:Description and rdf:value.
+ */
+ private fun parseRdfResourcePropertyElement(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Element,
+ isTopLevel: Boolean,
+ options: ParseOptions
+ ) {
+
+ // Strip old "punchcard" chaff which has on the prefix "iX:".
+ if (isTopLevel && "iX:changes" == xmlNode.nodeName)
+ return
+
+ val newCompound = addChildNode(xmp, xmpParent, xmlNode, "", isTopLevel)
+
+ // walk through the attributes
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 0 until xmlNode.attributes.length) {
+
+ val attribute = xmlNode.attributes.item(index) as Attr
+
+ if ("xmlns" == attribute.prefix || attribute.prefix == null && "xmlns" == attribute.nodeName)
+ continue
+
+ if (XMPConst.XML_LANG == attribute.nodeName)
+ addQualifierNode(newCompound, XMPConst.XML_LANG, attribute.value)
+ else if ("ID" == attribute.localName && XMPConst.NS_RDF == attribute.namespaceURI)
+ continue // Ignore all rdf:ID attributes.
+ else
+ throw XMPException("Invalid attribute for resource property element", XMPError.BADRDF)
+ }
+
+ // walk through the children
+ var found = false
+
+ for (index in 0 until xmlNode.childNodes.length) {
+
+ val currentChild = xmlNode.childNodes.item(index)!!
+
+ if (!isWhitespaceNode(currentChild)) {
+
+ if (currentChild is Element && !found) {
+
+ val isRDF = XMPConst.NS_RDF == currentChild.namespaceURI
+
+ val localName = currentChild.localName
+
+ when {
+
+ isRDF && "Bag" == localName ->
+ newCompound.options.setArray(true)
+
+ isRDF && "Seq" == localName ->
+ newCompound.options.setArray(true).setArrayOrdered(true)
+
+ isRDF && "Alt" == localName ->
+ newCompound.options.setArray(true).setArrayOrdered(true).setArrayAlternate(true)
+
+ else -> {
+
+ newCompound.options.setStruct(true)
+
+ if (!isRDF && "Description" != localName) {
+
+ var typeName = currentChild.namespaceURI
+ ?: throw XMPException(
+ "All XML elements must be in a namespace", XMPError.BADXMP
+ )
+
+ typeName += ":$localName"
+
+ addQualifierNode(newCompound, "rdf:type", typeName)
+ }
+ }
+ }
+
+ parseRdfNodeElement(xmp, newCompound, currentChild, false, options)
+
+ if (newCompound.hasValueChild)
+ fixupQualifiedNode(newCompound)
+ else if (newCompound.options.isArrayAlternate())
+ XMPNodeUtils.detectAltText(newCompound)
+
+ found = true
+
+ } else if (found) {
+ // found second child element
+ throw XMPException("Invalid child of resource property element", XMPError.BADRDF)
+ } else {
+ throw XMPException(
+ "Children of resource property element must be XML elements", XMPError.BADRDF
+ )
+ }
+ }
+ }
+
+ if (!found)
+ throw XMPException("Missing child of resource property element", XMPError.BADRDF)
+ }
+
+ /**
+ * 7.2.16 literalPropertyElt
+ * start-element ( URI == propertyElementURIs,
+ * attributes == set ( idAttr?, datatypeAttr?) )
+ * text()
+ * end-element()
+ *
+ * Add a leaf node with the text value and qualifiers for the attributes.
+ */
+ private fun parseRdfLiteralPropertyElement(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Element,
+ isTopLevel: Boolean
+ ) {
+
+ val newChild = addChildNode(xmp, xmpParent, xmlNode, null, isTopLevel)
+
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 0 until xmlNode.attributes.length) {
+
+ val attribute = xmlNode.attributes.item(index) as Attr
+
+ if ("xmlns" == attribute.prefix || attribute.prefix == null && "xmlns" == attribute.nodeName)
+ continue
+
+ if (XMPConst.XML_LANG == attribute.nodeName)
+ addQualifierNode(newChild, XMPConst.XML_LANG, attribute.value)
+ else if (
+ XMPConst.NS_RDF == attribute.namespaceURI &&
+ ("ID" == attribute.localName || "datatype" == attribute.localName)
+ )
+ continue // Ignore all rdf:ID and rdf:datatype attributes.
+ else
+ throw XMPException("Invalid attribute for literal property element", XMPError.BADRDF)
+ }
+
+ var textValue = ""
+
+ for (index in 0 until xmlNode.childNodes.length) {
+
+ val child = xmlNode.childNodes.item(index)
+
+ if (child !is Text)
+ throw XMPException("Invalid child of literal property element", XMPError.BADRDF)
+
+ textValue += child.data
+ }
+
+ newChild.value = textValue
+ }
+
+ /**
+ * 7.2.18 parseTypeResourcePropertyElt
+ * start-element ( URI == propertyElementURIs,
+ * attributes == set ( idAttr?, parseResource ) )
+ * propertyEltList
+ * end-element()
+ *
+ * Add a new struct node with a qualifier for the possible rdf:ID attribute.
+ * Then process the XML child nodes to get the struct fields.
+ */
+ private fun parseTypeResourcePropertyElement(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Element,
+ isTopLevel: Boolean,
+ options: ParseOptions
+ ) {
+
+ val newStruct = addChildNode(xmp, xmpParent, xmlNode, "", isTopLevel)
+
+ newStruct.options.setStruct(true)
+
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 0 until xmlNode.attributes.length) {
+
+ val attribute = xmlNode.attributes.item(index) as Attr
+
+ if ("xmlns" == attribute.prefix || attribute.prefix == null && "xmlns" == attribute.nodeName)
+ continue
+
+ if (XMPConst.XML_LANG == attribute.nodeName) {
+ addQualifierNode(newStruct, XMPConst.XML_LANG, attribute.value)
+ } else if (
+ XMPConst.NS_RDF == attribute.namespaceURI &&
+ ("ID" == attribute.localName || "parseType" == attribute.localName)
+ ) {
+ continue // The caller ensured the value is "Resource". Ignore all rdf:ID attributes.
+ } else {
+ throw XMPException(
+ "Invalid attribute for ParseTypeResource property element", XMPError.BADRDF
+ )
+ }
+ }
+
+ parseRdfPropertyElementList(xmp, newStruct, xmlNode, false, options)
+
+ if (newStruct.hasValueChild)
+ fixupQualifiedNode(newStruct)
+ }
+
+ /**
+ * 7.2.21 emptyPropertyElt
+ * start-element ( URI == propertyElementURIs,
+ * attributes == set (
+ * idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
+ * end-element()
+ *
+ *
+ *
+ *
+ *
+ *
+ * An emptyPropertyElt is an element with no contained content, just a possibly empty set of
+ * attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a
+ * simple property with an empty value (ns:Prop1), a simple property whose value is a URI
+ * (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3).
+ * An emptyPropertyElt can also represent an XMP struct whose fields are all simple and
+ * unqualified (ns:Prop4).
+ *
+ * It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the
+ * verbose form written using a literalPropertyElt.
+ *
+ * The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for
+ * design reasons and partly for historical reasons. The XMP mapping rules are:
+ *
+ * 1. If there is an rdf:value attribute then this is a simple property with a text value.
+ * All other attributes are qualifiers.
+ * 2. If there is an rdf:resource attribute then this is a simple property with a URI value.
+ * All other attributes are qualifiers.
+ * 3. If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID then this is a simple
+ * property with an empty value.
+ * 4. Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, or rdf:nodeID are fields.
+ */
+ private fun parseEmptyPropertyElement(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Element,
+ isTopLevel: Boolean
+ ) {
+
+ var hasPropertyAttrs = false
+ var hasResourceAttr = false
+ var hasNodeIDAttr = false
+ var hasValueAttr = false
+ var valueNode: Node? = null // ! Can come from rdf:value or rdf:resource.
+
+ if (xmlNode.childNodes.length > 0)
+ throw XMPException(
+ "Nested content not allowed with rdf:resource or property attributes", XMPError.BADRDF
+ )
+
+ // First figure out what XMP this maps to and remember the XML node for a simple value.
+ for (index in 0 until xmlNode.attributes.length) {
+
+ val attribute = xmlNode.attributes.item(index) as Attr
+
+ if ("xmlns" == attribute.prefix || attribute.prefix == null && "xmlns" == attribute.nodeName)
+ continue
+
+ val attrTerm = getRDFTermKind(attribute)
+
+ when (attrTerm) {
+
+ RDFTERM_ID -> {
+ /* Do nothing. */
+ }
+
+ RDFTERM_RESOURCE -> {
+
+ if (hasNodeIDAttr) {
+ throw XMPException(
+ "Empty property element can't have both rdf:resource and rdf:nodeID",
+ XMPError.BADRDF
+ )
+ } else if (hasValueAttr) {
+ throw XMPException(
+ "Empty property element can't have both rdf:value and rdf:resource",
+ XMPError.BADXMP
+ )
+ }
+
+ hasResourceAttr = true
+
+ if (!hasValueAttr)
+ valueNode = attribute
+ }
+
+ RDFTERM_NODE_ID -> {
+
+ if (hasResourceAttr) {
+ throw XMPException(
+ "Empty property element can't have both rdf:resource and rdf:nodeID",
+ XMPError.BADRDF
+ )
+ }
+
+ hasNodeIDAttr = true
+ }
+
+ RDFTERM_OTHER -> {
+
+ if (attribute.localName == "value" && attribute.namespaceURI == XMPConst.NS_RDF) {
+
+ if (hasResourceAttr) {
+ throw XMPException(
+ "Empty property element can't have both rdf:value and rdf:resource",
+ XMPError.BADXMP
+ )
+ }
+
+ hasValueAttr = true
+ valueNode = attribute
+
+ } else if (XMPConst.XML_LANG != attribute.nodeName) {
+
+ hasPropertyAttrs = true
+ }
+ }
+
+ else ->
+ throw XMPException("Unrecognized attribute of empty property element", XMPError.BADRDF)
+ }
+ }
+
+ // Create the right kind of child node and visit the attributes again
+ // to add the fields or qualifiers.
+ // ! Because of implementation vagaries,
+ // the xmpParent is the tree root for top level properties.
+ // ! The schema is found, created if necessary, by addChildNode.
+ val childNode = addChildNode(xmp, xmpParent, xmlNode, "", isTopLevel)
+
+ var childIsStruct = false
+
+ if (hasValueAttr || hasResourceAttr) {
+
+ val valueNodeValue = when (valueNode) {
+ null -> null
+ is Attr -> valueNode.value
+ else -> throw XMPException("Unknown Node ${xmlNode.nodeType}", XMPError.BADXMP)
+ }
+
+ childNode.value = valueNodeValue ?: ""
+
+ // ! Might have both rdf:value and rdf:resource.
+ if (!hasValueAttr)
+ childNode.options.setURI(true)
+
+ } else if (hasPropertyAttrs) {
+ childNode.options.setStruct(true)
+ childIsStruct = true
+ }
+
+ for (index in 0 until xmlNode.attributes.length) {
+
+ val attribute = xmlNode.attributes.item(index) as Attr
+
+ if (
+ attribute === valueNode || "xmlns" == attribute.prefix ||
+ attribute.prefix == null && "xmlns" == attribute.nodeName
+ )
+ continue // Skip the rdf:value or rdf:resource attribute holding the value.
+
+ val attrTerm = getRDFTermKind(attribute)
+
+ when (attrTerm) {
+
+ RDFTERM_ID, RDFTERM_NODE_ID -> {
+ /* Do nothing. */
+ }
+
+ RDFTERM_RESOURCE ->
+ addQualifierNode(childNode, "rdf:resource", attribute.value)
+
+ RDFTERM_OTHER -> {
+
+ if (!childIsStruct)
+ addQualifierNode(childNode, attribute.nodeName, attribute.value)
+ else if (XMPConst.XML_LANG == attribute.nodeName)
+ addQualifierNode(childNode, XMPConst.XML_LANG, attribute.value)
+ else
+ addChildNode(xmp, childNode, attribute, attribute.value, false)
+ }
+
+ else -> throw XMPException(
+ "Unrecognized attribute of empty property element",
+ XMPError.BADRDF
+ )
+ }
+ }
+ }
+
+ private fun addChildNode(
+ xmp: XMPMetaImpl,
+ xmpParent: XMPNode,
+ xmlNode: Node,
+ value: String?,
+ isTopLevel: Boolean
+ ): XMPNode {
+
+ var actualXmpParent = xmpParent
+
+ val registry = schemaRegistry
+
+ var namespace = when (xmlNode) {
+ is Element -> xmlNode.namespaceURI
+ is Attr -> xmlNode.namespaceURI
+ else -> throw XMPException("Unknown Node ${xmlNode.nodeType}", XMPError.BADXMP)
+ }
+
+ if (namespace.isNullOrEmpty())
+ throw XMPException(
+ "XML namespace required for all elements and attributes: $xmlNode",
+ XMPError.BADRDF
+ )
+
+ // Fix a legacy DC namespace
+ if (XMPConst.NS_DC_DEPRECATED == namespace)
+ namespace = XMPConst.NS_DC
+
+ var prefix = registry.getNamespacePrefix(namespace)
+
+ if (prefix == null) {
+
+ val xmlNodePrefix = when (xmlNode) {
+ is Element -> xmlNode.prefix
+ is Attr -> xmlNode.prefix
+ else -> throw XMPException("Unknown Node ${xmlNode.nodeType}", XMPError.BADXMP)
+ }
+
+ prefix = if (xmlNodePrefix != null)
+ xmlNodePrefix
+ else
+ DEFAULT_PREFIX
+
+ prefix = registry.registerNamespace(namespace, prefix)
+ }
+
+ val xmlNodeLocalName = when (xmlNode) {
+ is Element -> xmlNode.localName
+ is Attr -> xmlNode.localName
+ else -> throw XMPException("Unknown Node ${xmlNode.nodeType}", XMPError.BADXMP)
+ }
+
+ val childName = prefix + xmlNodeLocalName
+
+ // create schema node if not already there
+ val childOptions = PropertyOptions()
+
+ var isAlias = false
+
+ if (isTopLevel) {
+
+ // Lookup the schema node, adjust the XMP parent pointer.
+ // Incoming parent must be the tree root.
+ val schemaNode = XMPNodeUtils.findSchemaNode(
+ xmp.root, namespace,
+ DEFAULT_PREFIX, true
+ )
+
+ checkNotNull(schemaNode) { "SchemaNode should have been created." }
+
+ schemaNode.isImplicit = false // Clear the implicit node bit.
+
+ // *** Should use "opt &= ~flag" (no conditional),
+ // need runtime check for proper 32 bit code.
+ actualXmpParent = schemaNode
+
+ // If this is an alias set the alias flag in the node
+ // and the hasAliases flag in the tree.
+ if (registry.findAlias(childName) != null) {
+ isAlias = true
+ xmp.root.hasAliases = true
+ schemaNode.hasAliases = true
+ }
+ }
+
+ // Make sure that this is not a duplicate of a named node.
+ val isArrayItem = isNumberedArrayItemName(childName)
+ val isValueNode = "rdf:value" == childName
+
+ // Create XMP node and so some checks
+ val newChild = XMPNode(childName, value, childOptions)
+
+ newChild.isAlias = isAlias
+
+ // Add the new child to the XMP parent node, a value node first.
+ if (!isValueNode)
+ actualXmpParent.addChild(newChild)
+ else
+ actualXmpParent.addChild(1, newChild)
+
+ if (isValueNode) {
+
+ if (isTopLevel || !actualXmpParent.options.isStruct())
+ throw XMPException("Misplaced rdf:value element", XMPError.BADRDF)
+
+ actualXmpParent.hasValueChild = true
+ }
+
+ val isParentArray = actualXmpParent.options.isArray()
+
+ when {
+
+ isParentArray && isArrayItem ->
+ newChild.name = XMPConst.ARRAY_ITEM_NAME
+
+ !isParentArray && isArrayItem ->
+ throw XMPException("Misplaced rdf:li element", XMPError.BADRDF)
+
+ isParentArray && !isArrayItem ->
+ throw XMPException("Arrays cannot have arbitrary child names", XMPError.BADRDF)
+ }
+
+ return newChild
+ }
+
+ private fun addQualifierNode(xmpParent: XMPNode, name: String, value: String): XMPNode {
+
+ val isLang = XMPConst.XML_LANG == name
+
+ // normalize value of language qualifiers
+ val normalizedValue = if (isLang)
+ Utils.normalizeLangValue(value)
+ else
+ value
+
+ val newQualifier = XMPNode(name, normalizedValue)
+
+ xmpParent.addQualifier(newQualifier)
+
+ return newQualifier
+ }
+
+ /**
+ * The parent is an RDF pseudo-struct containing an rdf:value field. Fix the
+ * XMP data model. The rdf:value node must be the first child, the other
+ * children are qualifiers. The form, value, and children of the rdf:value
+ * node are the real ones. The rdf:value node's qualifiers must be added to
+ * the others.
+ */
+ private fun fixupQualifiedNode(xmpParent: XMPNode) {
+
+ require(xmpParent.options.isStruct() && xmpParent.hasChildren())
+
+ val valueNode = xmpParent.getChild(1)
+
+ require("rdf:value" == valueNode.name)
+
+ // Move the qualifiers on the value node to the parent.
+ // Make sure an xml:lang qualifier stays at the front.
+ // Check for duplicate names between the value node's qualifiers and the parent's children.
+ // The parent's children are about to become qualifiers. Check here, between the groups.
+ // Intra-group duplicates are caught by XMPNode#addChild(...).
+
+ if (valueNode.options.hasLanguage()) {
+
+ if (xmpParent.options.hasLanguage())
+ throw XMPException("Redundant xml:lang for rdf:value element", XMPError.BADXMP)
+
+ val langQual = valueNode.getQualifier(1)
+
+ valueNode.removeQualifier(langQual)
+
+ xmpParent.addQualifier(langQual)
+ }
+
+ // Start the remaining copy after the xml:lang qualifier.
+ for (index in 1..valueNode.getQualifierLength()) {
+
+ val qualifier = valueNode.getQualifier(index)
+
+ xmpParent.addQualifier(qualifier)
+ }
+
+ // Change the parent's other children into qualifiers.
+ // This loop starts at 1, child 0 is the rdf:value node.
+ for (index in 2..xmpParent.getChildrenLength()) {
+
+ val qualifier = xmpParent.getChild(index)
+
+ xmpParent.addQualifier(qualifier)
+ }
+
+ check(xmpParent.options.isStruct() || xmpParent.hasValueChild)
+
+ xmpParent.hasValueChild = false
+ xmpParent.options.setStruct(false)
+ xmpParent.options.mergeWith(valueNode.options)
+ xmpParent.value = valueNode.value
+ xmpParent.removeChildren()
+
+ for (child in valueNode.getChildren())
+ xmpParent.addChild(child)
+ }
+
+ /**
+ * Checks if the node is a white space.
+ *
+ * @param node an XML-node
+ * @return Returns whether the node is a whitespace node, i.e. a text node that contains only whitespaces.
+ */
+ private fun isWhitespaceNode(node: Node): Boolean {
+
+ if (node !is Text)
+ return false
+
+ val value = node.data
+
+ for (index in 0 until value.length)
+ if (!value[index].isWhitespace())
+ return false
+
+ return true
+ }
+
+ /**
+ * 7.2.6 propertyElementURIs
+ * anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms )
+ */
+ private fun isPropertyElementName(term: Int): Boolean {
+
+ if (term == RDFTERM_DESCRIPTION || isOldTerm(term))
+ return false
+
+ return !isCoreSyntaxTerm(term)
+ }
+
+ /**
+ * 7.2.4 oldTerms
+ * rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID
+ *
+ * @param term the term id
+ * @return Returns true if the term is an old term.
+ */
+ private fun isOldTerm(term: Int): Boolean =
+ RDFTERM_FIRST_OLD <= term && term <= RDFTERM_LAST_OLD
+
+ /**
+ * 7.2.2 coreSyntaxTerms
+ * rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID |
+ * rdf:datatype
+ *
+ * @param term the term id
+ * @return Return true if the term is a core syntax term
+ */
+ private fun isCoreSyntaxTerm(term: Int): Boolean =
+ RDFTERM_FIRST_CORE <= term && term <= RDFTERM_LAST_CORE
+
+ /**
+ * Determines the ID for a certain RDF Term.
+ * Arranged to hopefully minimize the parse time for large XMP.
+ *
+ * @param node an XML node
+ * @return Returns the term ID.
+ */
+ private fun getRDFTermKind(node: Node): Int {
+
+ val nodeName = node.nodeName
+
+ var namespace = when (node) {
+ is Element -> node.namespaceURI
+ is Attr -> node.namespaceURI
+ else -> throw XMPException("Unknown Node ${node.nodeType}", XMPError.BADXMP)
+ }
+
+ if (namespace == null &&
+ ("about" == nodeName || "ID" == nodeName) &&
+ node is Attr && XMPConst.NS_RDF == node.ownerElement?.namespaceURI
+ ) {
+ namespace = XMPConst.NS_RDF
+ }
+
+ if (namespace == XMPConst.NS_RDF) {
+
+ when (nodeName) {
+
+ "rdf:li" ->
+ return RDFTERM_LI
+
+ "parseType" ->
+ return RDFTERM_PARSE_TYPE
+
+ "rdf:Description" ->
+ return RDFTERM_DESCRIPTION
+
+ "rdf:about" ->
+ return RDFTERM_ABOUT
+
+ "resource" ->
+ return RDFTERM_RESOURCE
+
+ "rdf:RDF" ->
+ return RDFTERM_RDF
+
+ "ID" ->
+ return RDFTERM_ID
+
+ "nodeID" ->
+ return RDFTERM_NODE_ID
+
+ "datatype" ->
+ return RDFTERM_DATATYPE
+
+ "aboutEach" ->
+ return RDFTERM_ABOUT_EACH
+
+ "aboutEachPrefix" ->
+ return RDFTERM_ABOUT_EACH_PREFIX
+
+ "bagID" ->
+ return RDFTERM_BAG_ID
+ }
+ }
+
+ return RDFTERM_OTHER
+ }
+
+ private fun isNumberedArrayItemName(nodeName: String): Boolean {
+
+ var result = "rdf:li" == nodeName
+
+ if (nodeName.startsWith("rdf:_")) {
+
+ result = true
+
+ for (i in 5 until nodeName.length)
+ result = result && nodeName[i] >= '0' && nodeName[i] <= '9'
+ }
+
+ return result
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPRDFWriter.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPRDFWriter.kt
new file mode 100644
index 0000000..2212624
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPRDFWriter.kt
@@ -0,0 +1,993 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPMetaFactory.schemaRegistry
+import com.ashampoo.xmp.XMPMetaFactory.versionInfo
+import com.ashampoo.xmp.impl.Utils.escapeXML
+import com.ashampoo.xmp.options.SerializeOptions
+
+/**
+ * Serializes the `XMPMeta`-object using the standard RDF serialization format.
+ * The output is a XMP String according to the `SerializeOptions`.
+ */
+internal class XMPRDFWriter(
+ val xmp: XMPMetaImpl,
+ val options: SerializeOptions
+) {
+
+ private val sb: StringBuilder = StringBuilder()
+
+ /**
+ * The actual serialization.
+ */
+ fun serialize(): String {
+
+ try {
+
+ sb.clear()
+
+ serializeAsRDF()
+
+ return sb.toString()
+
+ } catch (ex: Exception) {
+ throw XMPException("Error writing the XMP", XMPError.UNKNOWN, ex)
+ }
+ }
+
+ /**
+ * Writes the (optional) packet header and the outer rdf-tags.
+ */
+ private fun serializeAsRDF() {
+
+ var level = 0
+
+ // Write the packet header PI.
+ if (!options.getOmitPacketWrapper()) {
+ writeIndent(level)
+ write(PACKET_HEADER)
+ writeNewline()
+ }
+
+ // Write the x:xmpmeta element's start tag.
+ if (!options.getOmitXmpMetaElement()) {
+
+ writeIndent(level)
+ write(RDF_XMPMETA_START)
+ write(versionInfo.message)
+ write("\">")
+ writeNewline()
+
+ level++
+ }
+
+ // Write the rdf:RDF start tag.
+ writeIndent(level)
+ write(RDF_RDF_START)
+ writeNewline()
+
+ // Write all of the properties.
+ if (options.getUseCanonicalFormat())
+ serializeCanonicalRDFSchemas(level)
+ else
+ serializeCompactRDFSchemas(level)
+
+ // Write the rdf:RDF end tag.
+ writeIndent(level)
+ write(RDF_RDF_END)
+ writeNewline()
+
+ // Write the xmpmeta end tag.
+ if (!options.getOmitXmpMetaElement()) {
+
+ level--
+
+ writeIndent(level)
+ write(RDF_XMPMETA_END)
+ writeNewline()
+ }
+
+ // Write the packet trailer PI into the tail string as UTF-8.
+ var tailStr = ""
+
+ if (!options.getOmitPacketWrapper()) {
+
+ level = 0
+
+ while (level > 0) {
+ tailStr += XMP_DEFAULT_INDENT
+ level--
+ }
+
+ tailStr += PACKET_TRAILER
+ tailStr += if (options.getReadOnlyPacket()) 'r' else 'w'
+ tailStr += PACKET_TRAILER2
+ }
+
+ write(tailStr)
+ }
+
+ /**
+ * Serializes the metadata in pretty-printed manner.
+ *
+ * @param level indent level
+ */
+ private fun serializeCanonicalRDFSchemas(level: Int) {
+
+ if (xmp.root.hasChildren()) {
+
+ startOuterRDFDescription(xmp.root, level)
+
+ for (schema in xmp.root.getChildren())
+ serializeCanonicalRDFSchema(schema, level)
+
+ endOuterRDFDescription(level)
+
+ } else {
+
+ writeIndent(level + 1)
+ write(RDF_SCHEMA_START) // Special case an empty XMP object.
+ writeTreeName()
+ write("/>")
+ writeNewline()
+ }
+ }
+
+ private fun writeTreeName() {
+
+ write('"')
+
+ val name = xmp.root.name
+
+ if (name != null)
+ appendNodeValue(name, true)
+
+ write('"')
+ }
+
+ /**
+ * Serializes the metadata in compact manner.
+ *
+ * @param level indent level to start with
+ */
+ private fun serializeCompactRDFSchemas(level: Int) {
+
+ // Begin the rdf:Description start tag.
+ writeIndent(level + 1)
+ write(RDF_SCHEMA_START)
+ writeTreeName()
+
+ // Write all necessary xmlns attributes.
+ val usedPrefixes: MutableSet = mutableSetOf()
+ usedPrefixes.add("xml")
+ usedPrefixes.add("rdf")
+
+ for (schema in xmp.root.getChildren())
+ declareUsedNamespaces(schema, usedPrefixes, level + 3)
+
+ // Write the top level "attrProps" and close the rdf:Description start tag.
+ var allAreAttrs = true
+
+ for (schema in xmp.root.getChildren())
+ allAreAttrs = allAreAttrs and serializeCompactRDFAttrProps(schema, level + 2)
+
+ if (!allAreAttrs) {
+
+ write('>')
+ writeNewline()
+
+ } else {
+
+ write("/>")
+ writeNewline()
+ return // ! Done if all properties in all schema are written as attributes.
+ }
+
+ // Write the remaining properties for each schema.
+ for (schema in xmp.root.getChildren())
+ serializeCompactRDFElementProps(schema, level + 2)
+
+ // Write the rdf:Description end tag.
+ // *** Elide the end tag if everything (all props in all schema) is an attr.
+ writeIndent(level + 1)
+ write(RDF_SCHEMA_END)
+ writeNewline()
+ }
+
+ /**
+ * Write each of the parent's simple unqualified properties as an attribute. Returns true if all
+ * of the properties are written as attributes.
+ *
+ * @param parentNode the parent property node
+ * @param indent the current indent level
+ * @return Returns true if all properties can be rendered as RDF attribute.
+ */
+ private fun serializeCompactRDFAttrProps(parentNode: XMPNode, indent: Int): Boolean {
+
+ var allAreAttrs = true
+
+ for (prop in parentNode.getChildren()) {
+
+ if (canBeRDFAttrProp(prop)) {
+
+ writeNewline()
+ writeIndent(indent)
+ write(prop.name!!)
+ write("=\"")
+ appendNodeValue(prop.value, true)
+ write('"')
+
+ } else {
+
+ allAreAttrs = false
+ }
+ }
+
+ return allAreAttrs
+ }
+
+ /**
+ * Recursively handles the "value" for a node that must be written as an RDF
+ * property element. It does not matter if it is a top level property, a
+ * field of a struct, or an item of an array. The indent is that for the
+ * property element. The patterns bwlow ignore attribute qualifiers such as
+ * xml:lang, they don't affect the output form.
+ *
+ * @param parentNode the parent node
+ * @param indent the current indent level
+ */
+ private fun serializeCompactRDFElementProps(parentNode: XMPNode, indent: Int) {
+
+ for (node in parentNode.getChildren()) {
+
+ if (canBeRDFAttrProp(node))
+ continue
+
+ var emitEndTag = true
+ var indentEndTag = true
+
+ // Determine the XML element name, write the name part of the start tag. Look over the
+ // qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute
+ // qualifiers at the same time.
+ var elemName = node.name
+
+ if (XMPConst.ARRAY_ITEM_NAME == elemName)
+ elemName = "rdf:li"
+
+ writeIndent(indent)
+ write('<')
+ write(elemName!!)
+
+ var hasGeneralQualifiers = false
+ var hasRDFResourceQual = false
+
+ for (qualifier in node.getQualifier()) {
+
+ if (!RDF_ATTR_QUALIFIER.contains(qualifier.name)) {
+
+ hasGeneralQualifiers = true
+
+ } else {
+
+ hasRDFResourceQual = "rdf:resource" == qualifier.name
+ write(' ')
+ write(qualifier.name!!)
+ write("=\"")
+ appendNodeValue(qualifier.value, true)
+ write('"')
+ }
+ }
+
+ // Process the property according to the standard patterns.
+ if (hasGeneralQualifiers) {
+
+ serializeCompactRDFGeneralQualifier(indent, node)
+
+ } else {
+
+ // This node has only attribute qualifiers. Emit as a property element.
+ if (!node.options.isCompositeProperty()) {
+
+ val result = serializeCompactRDFSimpleProp(node)
+
+ emitEndTag = result[0] as Boolean
+ indentEndTag = result[1] as Boolean
+
+ } else if (node.options.isArray()) {
+
+ serializeCompactRDFArrayProp(node, indent)
+
+ } else {
+
+ emitEndTag = serializeCompactRDFStructProp(node, indent, hasRDFResourceQual)
+ }
+ }
+
+ // Emit the property element end tag.
+ if (emitEndTag) {
+
+ if (indentEndTag)
+ writeIndent(indent)
+
+ write("")
+ write(elemName)
+ write('>')
+ writeNewline()
+ }
+ }
+ }
+
+ /**
+ * Serializes a simple property.
+ *
+ * @param node an XMPNode
+ * @return Returns an array containing the flags emitEndTag and indentEndTag.
+ */
+ private fun serializeCompactRDFSimpleProp(node: XMPNode): Array {
+
+ // This is a simple property.
+ var emitEndTag = true
+ var indentEndTag = true
+
+ if (node.options.isURI()) {
+
+ write(" rdf:resource=\"")
+ appendNodeValue(node.value, true)
+ write("\"/>")
+ writeNewline()
+ emitEndTag = false
+
+ } else if (node.value == null || node.value?.length == 0) {
+
+ write("/>")
+ writeNewline()
+ emitEndTag = false
+
+ } else {
+
+ write('>')
+ appendNodeValue(node.value, false)
+ indentEndTag = false
+ }
+
+ return arrayOf(emitEndTag, indentEndTag)
+ }
+
+ /**
+ * Serializes an array property.
+ *
+ * @param node an XMPNode
+ * @param indent the current indent level
+ */
+ private fun serializeCompactRDFArrayProp(node: XMPNode, indent: Int) {
+
+ // This is an array.
+ write('>')
+ writeNewline()
+ emitRDFArrayTag(node, true, indent + 1)
+
+ if (node.options.isArrayAltText())
+ XMPNodeUtils.normalizeLangArray(node)
+
+ serializeCompactRDFElementProps(node, indent + 2)
+ emitRDFArrayTag(node, false, indent + 1)
+ }
+
+ /**
+ * Serializes a struct property.
+ *
+ * @param node an XMPNode
+ * @param indent the current indent level
+ * @param hasRDFResourceQual Flag if the element has resource qualifier
+ * @return Returns true if an end flag shall be emitted.
+ */
+ private fun serializeCompactRDFStructProp(
+ node: XMPNode,
+ indent: Int,
+ hasRDFResourceQual: Boolean
+ ): Boolean {
+
+ // This must be a struct.
+ var hasAttrFields = false
+ var hasElemFields = false
+ var emitEndTag = true
+
+ for (field in node.getChildren()) {
+
+ if (canBeRDFAttrProp(field))
+ hasAttrFields = true
+ else
+ hasElemFields = true
+
+ if (hasAttrFields && hasElemFields)
+ break // No sense looking further.
+ }
+
+ if (hasRDFResourceQual && hasElemFields)
+ throw XMPException("Can't mix rdf:resource qualifier and element fields", XMPError.BADRDF)
+
+ when {
+
+ !node.hasChildren() -> {
+
+ // Catch an empty struct as a special case. The case
+ // below would emit an empty
+ // XML element, which gets reparsed as a simple property
+ // with an empty value.
+ write(" rdf:parseType=\"Resource\"/>")
+ writeNewline()
+ emitEndTag = false
+ }
+
+ !hasElemFields -> {
+
+ // All fields can be attributes, use the
+ // emptyPropertyElt form.
+ serializeCompactRDFAttrProps(node, indent + 1)
+ write("/>")
+ writeNewline()
+ emitEndTag = false
+ }
+
+ !hasAttrFields -> {
+
+ // All fields must be elements, use the
+ // parseTypeResourcePropertyElt form.
+ write(" rdf:parseType=\"Resource\">")
+ writeNewline()
+ serializeCompactRDFElementProps(node, indent + 1)
+ }
+
+ else -> {
+
+ // Have a mix of attributes and elements, use an inner rdf:Description.
+ write('>')
+ writeNewline()
+ writeIndent(indent + 1)
+ write(RDF_STRUCT_START)
+ serializeCompactRDFAttrProps(node, indent + 2)
+ write(">")
+ writeNewline()
+ serializeCompactRDFElementProps(node, indent + 1)
+ writeIndent(indent + 1)
+ write(RDF_STRUCT_END)
+ writeNewline()
+ }
+ }
+
+ return emitEndTag
+ }
+
+ /**
+ * Serializes the general qualifier.
+ *
+ * @param node the root node of the subtree
+ * @param indent the current indent level
+ */
+ private fun serializeCompactRDFGeneralQualifier(indent: Int, node: XMPNode) {
+
+ // The node has general qualifiers, ones that can't be
+ // attributes on a property element.
+ // Emit using the qualified property pseudo-struct form. The
+ // value is output by a call
+ // to SerializePrettyRDFProperty with emitAsRDFValue set.
+
+ // *** We're losing compactness in the calls to SerializePrettyRDFProperty.
+ // *** Should refactor to have SerializeCompactRDFProperty that does one node.
+ write(" rdf:parseType=\"Resource\">")
+ writeNewline()
+ serializeCanonicalRDFProperty(node, false, true, indent + 1)
+
+ for (qualifier in node.getQualifier())
+ serializeCanonicalRDFProperty(qualifier, false, false, indent + 1)
+ }
+
+ /**
+ * Serializes one schema with all contained properties in pretty-printed
+ * manner.
+ *
+ * Each schema's properties are written to a single
+ * rdf:Description element. All of the necessary namespaces are declared in
+ * the rdf:Description element. The baseIndent is the base level for the
+ * entire serialization, that of the x:xmpmeta element. An xml:lang
+ * qualifier is written as an attribute of the property start tag, not by
+ * itself forcing the qualified property form.
+ */
+ private fun serializeCanonicalRDFSchema(schemaNode: XMPNode, level: Int) {
+
+ // Write each of the schema's actual properties.
+
+ for (propNode in schemaNode.getChildren())
+ serializeCanonicalRDFProperty(propNode, options.getUseCanonicalFormat(), false, level + 2)
+ }
+
+ /**
+ * Writes all used namespaces of the subtree in node to the output.
+ * The subtree is recursivly traversed.
+ */
+ private fun declareUsedNamespaces(node: XMPNode, usedPrefixes: MutableSet, indent: Int) {
+
+ if (node.options.isSchemaNode()) {
+
+ // The schema node name is the URI, the value is the prefix.
+ val prefix = node.value!!.substring(0, node.value!!.length - 1)
+ declareNamespace(prefix, node.name, usedPrefixes, indent)
+
+ } else if (node.options.isStruct()) {
+
+ for (field in node.getChildren())
+ declareNamespace(field.name!!, null, usedPrefixes, indent)
+ }
+
+ for (child in node.getChildren())
+ declareUsedNamespaces(child, usedPrefixes, indent)
+
+ for (qualifier in node.getQualifier()) {
+
+ declareNamespace(qualifier.name!!, null, usedPrefixes, indent)
+ declareUsedNamespaces(qualifier, usedPrefixes, indent)
+ }
+ }
+
+ /**
+ * Writes one namespace declaration to the output.
+ *
+ * @param prefix a namespace prefix (without colon) or a complete qname (when namespace == null)
+ * @param namespace the a namespace
+ * @param usedPrefixes a set containing currently used prefixes
+ * @param indent the current indent level
+ */
+ private fun declareNamespace(
+ prefix: String,
+ namespace: String?,
+ usedPrefixes: MutableSet,
+ indent: Int
+ ) {
+
+ var prefix = prefix
+ var namespace = namespace
+
+ if (namespace == null) {
+
+ // prefix contains qname, extract prefix and lookup namespace with prefix
+ val qname = QName(prefix)
+
+ if (!qname.hasPrefix())
+ return
+
+ prefix = qname.prefix!!
+
+ // add colon for lookup
+ namespace = schemaRegistry.getNamespaceURI("$prefix:")
+
+ // prefix w/o colon
+ declareNamespace(prefix, namespace, usedPrefixes, indent)
+ }
+
+ if (!usedPrefixes.contains(prefix)) {
+
+ writeNewline()
+ writeIndent(indent)
+ write("xmlns:")
+ write(prefix)
+ write("=\"")
+ write(namespace!!)
+ write('"')
+
+ usedPrefixes.add(prefix)
+ }
+ }
+
+ /**
+ * Start the outer rdf:Description element, including all needed xmlns attributes.
+ * Leave the element open so that the compact form can add property attributes.
+ */
+ private fun startOuterRDFDescription(schemaNode: XMPNode, level: Int) {
+
+ writeIndent(level + 1)
+ write(RDF_SCHEMA_START)
+ writeTreeName()
+
+ val usedPrefixes: MutableSet = mutableSetOf()
+ usedPrefixes.add("xml")
+ usedPrefixes.add("rdf")
+
+ declareUsedNamespaces(schemaNode, usedPrefixes, level + 3)
+
+ write('>')
+ writeNewline()
+ }
+
+ /**
+ * Write the end tag.
+ */
+ private fun endOuterRDFDescription(level: Int) {
+
+ writeIndent(level + 1)
+ write(RDF_SCHEMA_END)
+ writeNewline()
+ }
+
+ /**
+ * Recursively handles the "value" for a node. It does not matter if it is a
+ * top level property, a field of a struct, or an item of an array. The
+ * indent is that for the property element. An xml:lang qualifier is written
+ * as an attribute of the property start tag, not by itself forcing the
+ * qualified property form. The patterns below mostly ignore attribute
+ * qualifiers like xml:lang. Except for the one struct case, attribute
+ * qualifiers don't affect the output form.
+ *
+ * @param node the property node
+ * @param emitAsRDFValue property shall be rendered as attribute rather than tag
+ * @param useCanonicalRDF use canonical form with inner description tag or
+ * the compact form with rdf:ParseType="resource" attribute.
+ * @param indent the current indent level
+ */
+ private fun serializeCanonicalRDFProperty(
+ node: XMPNode,
+ useCanonicalRDF: Boolean,
+ emitAsRDFValue: Boolean,
+ indent: Int
+ ) {
+
+ var indent = indent
+ var emitEndTag = true
+ var indentEndTag = true
+
+ // Determine the XML element name. Open the start tag with the name and
+ // attribute qualifiers.
+ var elemName = node.name
+
+ if (emitAsRDFValue)
+ elemName = "rdf:value"
+ else if (XMPConst.ARRAY_ITEM_NAME == elemName)
+ elemName = "rdf:li"
+
+ writeIndent(indent)
+ write('<')
+ write(elemName!!)
+
+ var hasGeneralQualifiers = false
+ var hasRDFResourceQual = false
+
+ val it = node.iterateQualifier()
+
+ while (it.hasNext()) {
+
+ val qualifier = it.next()
+
+ if (!RDF_ATTR_QUALIFIER.contains(qualifier.name)) {
+
+ hasGeneralQualifiers = true
+
+ } else {
+
+ hasRDFResourceQual = "rdf:resource" == qualifier.name
+
+ if (!emitAsRDFValue) {
+
+ write(' ')
+ write(qualifier.name!!)
+ write("=\"")
+ appendNodeValue(qualifier.value, true)
+ write('"')
+ }
+ }
+ }
+
+ // Process the property according to the standard patterns.
+ if (hasGeneralQualifiers && !emitAsRDFValue) {
+
+ // This node has general, non-attribute, qualifiers. Emit using the
+ // qualified property form.
+ // ! The value is output by a recursive call ON THE SAME NODE with
+ // emitAsRDFValue set.
+ if (hasRDFResourceQual)
+ throw XMPException("Can't mix rdf:resource and general qualifiers", XMPError.BADRDF)
+
+ // Change serialization to canonical format with inner rdf:Description-tag
+ // depending on option
+ if (useCanonicalRDF) {
+
+ write(">")
+ writeNewline()
+ indent++
+ writeIndent(indent)
+ write(RDF_STRUCT_START)
+ write(">")
+
+ } else {
+ write(" rdf:parseType=\"Resource\">")
+ }
+
+ writeNewline()
+
+ serializeCanonicalRDFProperty(node, useCanonicalRDF, true, indent + 1)
+
+ for (qualifier in node.getQualifier())
+ if (!RDF_ATTR_QUALIFIER.contains(qualifier.name))
+ serializeCanonicalRDFProperty(qualifier, useCanonicalRDF, false, indent + 1)
+
+ if (useCanonicalRDF) {
+
+ writeIndent(indent)
+ write(RDF_STRUCT_END)
+ writeNewline()
+ indent--
+ }
+
+ } else {
+
+ // This node has no general qualifiers. Emit using an unqualified form.
+ when {
+
+ !node.options.isCompositeProperty() -> {
+
+ // This is a simple property.
+ if (node.options.isURI()) {
+
+ write(" rdf:resource=\"")
+ appendNodeValue(node.value, true)
+ write("\"/>")
+ writeNewline()
+
+ emitEndTag = false
+
+ } else if (node.value == null || "" == node.value) {
+
+ write("/>")
+ writeNewline()
+
+ emitEndTag = false
+
+ } else {
+
+ write('>')
+ appendNodeValue(node.value, false)
+
+ indentEndTag = false
+ }
+
+ }
+
+ node.options.isArray() -> {
+
+ // This is an array.
+ write('>')
+ writeNewline()
+ emitRDFArrayTag(node, true, indent + 1)
+
+ if (node.options.isArrayAltText())
+ XMPNodeUtils.normalizeLangArray(node)
+
+ for (child in node.getChildren())
+ serializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 2)
+
+ emitRDFArrayTag(node, false, indent + 1)
+ }
+
+ !hasRDFResourceQual -> {
+
+ // This is a "normal" struct, use the rdf:parseType="Resource" form.
+ if (!node.hasChildren()) {
+
+ // Change serialization to canonical format with inner rdf:Description-tag
+ // if option is set
+ if (useCanonicalRDF) {
+
+ write(">")
+ writeNewline()
+ writeIndent(indent + 1)
+ write(RDF_EMPTY_STRUCT)
+
+ } else {
+
+ write(" rdf:parseType=\"Resource\"/>")
+
+ emitEndTag = false
+ }
+
+ writeNewline()
+
+ } else {
+
+ // Change serialization to canonical format with inner rdf:Description-tag
+ // if option is set
+ if (useCanonicalRDF) {
+
+ write(">")
+ writeNewline()
+ indent++
+ writeIndent(indent)
+ write(RDF_STRUCT_START)
+ write(">")
+
+ } else {
+
+ write(" rdf:parseType=\"Resource\">")
+ }
+
+ writeNewline()
+
+ for (child in node.getChildren())
+ serializeCanonicalRDFProperty(child, useCanonicalRDF, false, indent + 1)
+
+ if (useCanonicalRDF) {
+ writeIndent(indent)
+ write(RDF_STRUCT_END)
+ writeNewline()
+ indent--
+ }
+ }
+
+ }
+
+ else -> {
+
+ // This is a struct with an rdf:resource attribute, use the "empty property element" form.
+
+ for (child in node.getChildren()) {
+
+ if (!canBeRDFAttrProp(child))
+ throw XMPException("Can't mix rdf:resource and complex fields", XMPError.BADRDF)
+
+ writeNewline()
+ writeIndent(indent + 1)
+ write(' ')
+ write(child.name!!)
+ write("=\"")
+ appendNodeValue(child.value, true)
+ write('"')
+ }
+
+ write("/>")
+ writeNewline()
+
+ emitEndTag = false
+ }
+ }
+ }
+
+ // Emit the property element end tag.
+ if (emitEndTag) {
+
+ if (indentEndTag)
+ writeIndent(indent)
+
+ write("")
+ write(elemName)
+ write('>')
+ writeNewline()
+ }
+ }
+
+ /**
+ * Writes the array start and end tags.
+ *
+ * @param arrayNode an array node
+ * @param isStartTag flag if its the start or end tag
+ * @param indent the current indent level
+ */
+ private fun emitRDFArrayTag(arrayNode: XMPNode, isStartTag: Boolean, indent: Int) {
+
+ if (isStartTag || arrayNode.hasChildren()) {
+
+ writeIndent(indent)
+
+ write(if (isStartTag) "")
+ else
+ write(">")
+
+ writeNewline()
+ }
+ }
+
+ /**
+ * Serializes the node value in XML encoding. Its used for tag bodies and
+ * attributes. *Note:* The attribute is always limited by quotes,
+ * thats why `'` is never serialized. *Note:*
+ * Control chars are written unescaped, but if the user uses others than tab, LF
+ * and CR the resulting XML will become invalid.
+ *
+ * @param value the value of the node
+ * @param forAttribute flag if value is an attribute value
+ *
+ */
+ private fun appendNodeValue(value: String?, forAttribute: Boolean) =
+ write(escapeXML(value ?: "", forAttribute, true))
+
+ /**
+ * A node can be serialized as RDF-Attribute, if it meets the following conditions:
+ *
+ * * is not array item
+ * * don't has qualifier
+ * * is no URI
+ * * is no composite property
+ *
+ * @param node an XMPNode
+ * @return Returns true if the node serialized as RDF-Attribute
+ */
+ private fun canBeRDFAttrProp(node: XMPNode): Boolean =
+ !node.hasQualifier() && !node.options.isURI() && !node.options.isCompositeProperty() &&
+ XMPConst.ARRAY_ITEM_NAME != node.name
+
+ private fun writeIndent(times: Int) =
+ repeat(times) { sb.append(XMP_DEFAULT_INDENT) }
+
+ private fun write(c: Char) =
+ sb.append(c)
+
+ private fun write(str: String) =
+ sb.append(str)
+
+ /**
+ * Writes a newline.
+ */
+ private fun writeNewline() {
+ sb.append(XMP_DEFAULT_NEWLINE)
+ }
+
+ companion object {
+
+ /** linefeed (U+000A) is the standard XML line terminator. XMP defaults to it. */
+ const val XMP_DEFAULT_NEWLINE = "\n"
+
+ /** Two ASCII spaces (U+0020) are the default indent for XMP files. */
+ const val XMP_DEFAULT_INDENT = " "
+
+ private const val PACKET_HEADER = ""
+
+ /**
+ * The w/r is missing inbetween
+ */
+ private const val PACKET_TRAILER = ""
+
+ private const val RDF_XMPMETA_START = ""
+
+ private const val RDF_RDF_END = ""
+
+ private const val RDF_SCHEMA_START = ""
+
+ private const val RDF_STRUCT_START = " = setOf(
+ XMPConst.XML_LANG, "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID"
+ )
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPSchemaRegistryImpl.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPSchemaRegistryImpl.kt
new file mode 100644
index 0000000..4602af0
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPSchemaRegistryImpl.kt
@@ -0,0 +1,613 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPConst
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPSchemaRegistry
+import com.ashampoo.xmp.impl.Utils.isXMLNameNS
+import com.ashampoo.xmp.options.AliasOptions
+import com.ashampoo.xmp.properties.XMPAliasInfo
+
+/**
+ * The schema registry handles the namespaces, aliases and global options for the XMP Toolkit.
+ * There is only one single instance used by the toolkit.
+ */
+object XMPSchemaRegistryImpl : XMPSchemaRegistry {
+
+ /**
+ * a map from a namespace URI to its registered prefix
+ */
+ private val namespaceToPrefixMap: MutableMap = mutableMapOf()
+
+ /**
+ * a map from a prefix to the associated namespace URI
+ */
+ private val prefixToNamespaceMap: MutableMap = mutableMapOf()
+
+ /**
+ * a map of all registered aliases.
+ * The map is a relationship from a qname to an `XMPAliasInfo`-object.
+ */
+ private val aliasMap: MutableMap = mutableMapOf()
+
+ /**
+ * The pattern that must not be contained in simple properties
+ */
+ private val simpleProperyPattern = Regex("[/*?\\[\\]]")
+
+ /**
+ * Performs the initialisation of the registry with the default namespaces, aliases and global
+ * options.
+ */
+ init {
+ try {
+
+ registerStandardNamespaces()
+ registerStandardAliases()
+
+ } catch (ex: XMPException) {
+ throw IllegalStateException("The XMPSchemaRegistry cannot be initialized!", ex)
+ }
+ }
+
+ // ---------------------------------------------------------------------------------------------
+ // Namespace Functions
+
+ override fun registerNamespace(namespaceURI: String, suggestedPrefix: String): String {
+
+ var suggestedPrefix = suggestedPrefix
+
+ if (namespaceURI.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (suggestedPrefix.isEmpty())
+ throw XMPException("Empty prefix", XMPError.BADPARAM)
+
+ if (suggestedPrefix[suggestedPrefix.length - 1] != ':')
+ suggestedPrefix += ':'
+
+ if (!isXMLNameNS(suggestedPrefix.substring(0, suggestedPrefix.length - 1)))
+ throw XMPException("The prefix is a bad XML name", XMPError.BADXML)
+
+ val registeredPrefix = namespaceToPrefixMap[namespaceURI]
+ val registeredNS = prefixToNamespaceMap[suggestedPrefix]
+
+ // Return the actual prefix
+ if (registeredPrefix != null)
+ return registeredPrefix
+
+ if (registeredNS != null) {
+
+ // the namespace is new, but the prefix is already engaged,
+ // we generate a new prefix out of the suggested
+ var generatedPrefix = suggestedPrefix
+
+ var i = 1
+
+ while (prefixToNamespaceMap.containsKey(generatedPrefix)) {
+ generatedPrefix =
+ suggestedPrefix.substring(0, suggestedPrefix.length - 1) + "_" + i + "_:"
+ i++
+ }
+
+ suggestedPrefix = generatedPrefix
+ }
+
+ prefixToNamespaceMap[suggestedPrefix] = namespaceURI
+ namespaceToPrefixMap[namespaceURI] = suggestedPrefix
+
+ // Return the suggested prefix
+ return suggestedPrefix
+ }
+
+ override fun deleteNamespace(namespaceURI: String) {
+
+ val prefixToDelete = getNamespacePrefix(namespaceURI) ?: return
+
+ namespaceToPrefixMap.remove(namespaceURI)
+ prefixToNamespaceMap.remove(prefixToDelete)
+ }
+
+ override fun getNamespacePrefix(namespaceURI: String): String? =
+ namespaceToPrefixMap[namespaceURI]
+
+ override fun getNamespaceURI(namespacePrefix: String): String? {
+
+ var namespacePrefix = namespacePrefix
+
+ if (!namespacePrefix.endsWith(":"))
+ namespacePrefix += ":"
+
+ return prefixToNamespaceMap[namespacePrefix]
+ }
+
+ override fun getNamespaces(): Map =
+ namespaceToPrefixMap
+
+ override fun getPrefixes(): Map =
+ prefixToNamespaceMap
+
+ /**
+ * Register the standard namespaces of schemas and types that are included in the XMP
+ * Specification and some other Adobe private namespaces.
+ * Note: This method is not lock because only called by the constructor.
+ */
+ private fun registerStandardNamespaces() {
+
+ // register standard namespaces
+ registerNamespace(XMPConst.NS_XML, "xml")
+ registerNamespace(XMPConst.NS_RDF, "rdf")
+ registerNamespace(XMPConst.NS_DC, "dc")
+ registerNamespace(XMPConst.NS_IPTCCORE, "Iptc4xmpCore")
+ registerNamespace(XMPConst.NS_IPTCEXT, "Iptc4xmpExt")
+ registerNamespace(XMPConst.NS_DICOM, "DICOM")
+ registerNamespace(XMPConst.NS_PLUS, "plus")
+
+ // register other common schemas
+ registerNamespace(XMPConst.NS_MWG_RS, "mwg-rs")
+ registerNamespace(XMPConst.NS_ACDSEE, "acdsee")
+
+ // register Adobe standard namespaces
+ registerNamespace(XMPConst.NS_X, "x")
+ registerNamespace(XMPConst.NS_IX, "iX")
+ registerNamespace(XMPConst.NS_XMP, "xmp")
+ registerNamespace(XMPConst.NS_XMP_RIGHTS, "xmpRights")
+ registerNamespace(XMPConst.NS_XMP_MM, "xmpMM")
+ registerNamespace(XMPConst.NS_XMP_BJ, "xmpBJ")
+ registerNamespace(XMPConst.NS_XMP_NOTE, "xmpNote")
+ registerNamespace(XMPConst.NS_PDF, "pdf")
+ registerNamespace(XMPConst.NS_PDFX, "pdfx")
+ registerNamespace(XMPConst.NS_PDFX_ID, "pdfxid")
+ registerNamespace(XMPConst.NS_PDFA_SCHEMA, "pdfaSchema")
+ registerNamespace(XMPConst.NS_PDFA_PROPERTY, "pdfaProperty")
+ registerNamespace(XMPConst.NS_PDFA_TYPE, "pdfaType")
+ registerNamespace(XMPConst.NS_PDFA_FIELD, "pdfaField")
+ registerNamespace(XMPConst.NS_PDFA_ID, "pdfaid")
+ registerNamespace(XMPConst.NS_PDFA_EXTENSION, "pdfaExtension")
+ registerNamespace(XMPConst.NS_PHOTOSHOP, "photoshop")
+ registerNamespace(XMPConst.NS_PSALBUM, "album")
+ registerNamespace(XMPConst.NS_EXIF, "exif")
+ registerNamespace(XMPConst.NS_EXIFX, "exifEX")
+ registerNamespace(XMPConst.NS_EXIF_AUX, "aux")
+ registerNamespace(XMPConst.NS_TIFF, "tiff")
+ registerNamespace(XMPConst.NS_PNG, "png")
+ registerNamespace(XMPConst.NS_JPEG, "jpeg")
+ registerNamespace(XMPConst.NS_JP2K, "jp2k")
+ registerNamespace(XMPConst.NS_CAMERARAW, "crs")
+ registerNamespace(XMPConst.NS_ADOBESTOCKPHOTO, "bmsp")
+ registerNamespace(XMPConst.NS_CREATOR_ATOM, "creatorAtom")
+ registerNamespace(XMPConst.NS_ASF, "asf")
+ registerNamespace(XMPConst.NS_WAV, "wav")
+ registerNamespace(XMPConst.NS_BWF, "bext")
+ registerNamespace(XMPConst.NS_RIFFINFO, "riffinfo")
+ registerNamespace(XMPConst.NS_SCRIPT, "xmpScript")
+ registerNamespace(XMPConst.NS_TXMP, "txmp")
+ registerNamespace(XMPConst.NS_SWF, "swf")
+
+ // register Adobe private namespaces
+ registerNamespace(XMPConst.NS_DM, "xmpDM")
+ registerNamespace(XMPConst.NS_TRANSIENT, "xmpx")
+
+ // register Adobe standard type namespaces
+ registerNamespace(XMPConst.TYPE_TEXT, "xmpT")
+ registerNamespace(XMPConst.TYPE_PAGEDFILE, "xmpTPg")
+ registerNamespace(XMPConst.TYPE_GRAPHICS, "xmpG")
+ registerNamespace(XMPConst.TYPE_IMAGE, "xmpGImg")
+ registerNamespace(XMPConst.TYPE_FONT, "stFnt")
+ registerNamespace(XMPConst.TYPE_DIMENSIONS, "stDim")
+ registerNamespace(XMPConst.TYPE_RESOURCEEVENT, "stEvt")
+ registerNamespace(XMPConst.TYPE_RESOURCEREF, "stRef")
+ registerNamespace(XMPConst.TYPE_ST_VERSION, "stVer")
+ registerNamespace(XMPConst.TYPE_ST_JOB, "stJob")
+ registerNamespace(XMPConst.TYPE_MANIFESTITEM, "stMfs")
+ registerNamespace(XMPConst.TYPE_IDENTIFIERQUAL, "xmpidq")
+ }
+
+ // ---------------------------------------------------------------------------------------------
+ // Alias Functions
+
+ override fun resolveAlias(aliasNS: String, aliasProp: String): XMPAliasInfo? {
+
+ val aliasPrefix = getNamespacePrefix(aliasNS) ?: return null
+
+ return aliasMap[aliasPrefix + aliasProp]
+ }
+
+ override fun findAlias(qname: String): XMPAliasInfo? =
+ aliasMap[qname]
+
+ override fun findAliases(aliasNS: String): Set {
+
+ val prefix = getNamespacePrefix(aliasNS)
+
+ if (prefix == null) return emptySet()
+
+ val result = mutableSetOf()
+
+ for (qname in aliasMap.keys) {
+
+ if (qname.startsWith(prefix)) {
+
+ val alias = findAlias(qname) ?: continue
+
+ result.add(alias)
+ }
+ }
+
+ return result
+ }
+
+ /**
+ * Associates an alias name with an actual name.
+ *
+ * Define a alias mapping from one namespace/property to another. Both
+ * property names must be simple names. An alias can be a direct mapping,
+ * where the alias and actual have the same data type. It is also possible
+ * to map a simple alias to an item in an array. This can either be to the
+ * first item in the array, or to the 'x-default' item in an alt-text array.
+ * Multiple alias names may map to the same actual, as long as the forms
+ * match. It is a no-op to reregister an alias in an identical fashion.
+ * Note: This method is not locking because only called by registerStandardAliases
+ * which is only called by the constructor.
+ * Note2: The method is only package-private so that it can be tested with unittests
+ *
+ * @param aliasNS The namespace URI for the alias. Must not be null or the empty
+ * string.
+ * @param aliasProp The name of the alias. Must be a simple name, not null or the
+ * empty string and not a general path expression.
+ * @param actualNS The namespace URI for the actual. Must not be null or the
+ * empty string.
+ * @param actualProp The name of the actual. Must be a simple name, not null or the
+ * empty string and not a general path expression.
+ * @param aliasForm Provides options for aliases for simple aliases to array
+ * items. This is needed to know what kind of array to create if
+ * set for the first time via the simple alias. Pass
+ * `XMP_NoOptions`, the default value, for all
+ * direct aliases regardless of whether the actual data type is
+ * an array or not (see [AliasOptions]).
+ */
+ fun registerAlias(
+ aliasNS: String,
+ aliasProp: String,
+ actualNS: String,
+ actualProp: String,
+ aliasForm: AliasOptions?
+ ) {
+
+ if (aliasNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (aliasProp.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ if (actualNS.isEmpty())
+ throw XMPException("Empty schema namespace URI", XMPError.BADPARAM)
+
+ if (actualProp.isEmpty())
+ throw XMPException("Empty property name", XMPError.BADPARAM)
+
+ // Fix the alias options
+ val aliasOpts = if (aliasForm != null)
+ AliasOptions(
+ XMPNodeUtils.verifySetOptions(
+ aliasForm.toPropertyOptions(),
+ null
+ ).getOptions()
+ ) else
+ AliasOptions()
+
+ if (simpleProperyPattern.matches(aliasProp) || simpleProperyPattern.matches(actualProp))
+ throw XMPException("Alias and actual property names must be simple", XMPError.BADXPATH)
+
+ // check if both namespaces are registered
+ val aliasPrefix = getNamespacePrefix(aliasNS)
+ val actualPrefix = getNamespacePrefix(actualNS)
+
+ if (aliasPrefix == null)
+ throw XMPException("Alias namespace is not registered", XMPError.BADSCHEMA)
+ else if (actualPrefix == null)
+ throw XMPException("Actual namespace is not registered", XMPError.BADSCHEMA)
+
+ val key = aliasPrefix + aliasProp
+
+ // check if alias is already existing
+ if (aliasMap.containsKey(key))
+ throw XMPException("Alias is already existing", XMPError.BADPARAM)
+ else if (aliasMap.containsKey(actualPrefix + actualProp))
+ throw XMPException(
+ "Actual property is already an alias, use the base property", XMPError.BADPARAM
+ )
+
+ val aliasInfo: XMPAliasInfo = object : XMPAliasInfo {
+
+ override fun getNamespace(): String = actualNS
+
+ override fun getPrefix(): String = actualPrefix
+
+ override fun getPropName(): String = actualProp
+
+ override fun getAliasForm(): AliasOptions = aliasOpts
+
+ override fun toString(): String =
+ actualPrefix + actualProp + " NS(" + actualNS + "), FORM (" + getAliasForm() + ")"
+ }
+
+ aliasMap[key] = aliasInfo
+ }
+
+ override fun getAliases(): Map =
+ aliasMap
+
+ /**
+ * Register the standard aliases.
+ * Note: This method is not lock because only called by the constructor.
+ */
+ private fun registerStandardAliases() {
+
+ val aliasToArrayOrdered = AliasOptions().setArrayOrdered(true)
+ val aliasToArrayAltText = AliasOptions().setArrayAltText(true)
+
+ // Aliases from XMP to DC.
+ registerAlias(
+ XMPConst.NS_XMP,
+ "Author",
+ XMPConst.NS_DC,
+ "creator",
+ aliasToArrayOrdered
+ )
+ registerAlias(
+ XMPConst.NS_XMP,
+ "Authors",
+ XMPConst.NS_DC,
+ "creator",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_XMP,
+ "Description",
+ XMPConst.NS_DC,
+ "description",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_XMP,
+ "Format",
+ XMPConst.NS_DC,
+ "format",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_XMP,
+ "Keywords",
+ XMPConst.NS_DC,
+ "subject",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_XMP,
+ "Locale",
+ XMPConst.NS_DC,
+ "language",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_XMP,
+ "Title",
+ XMPConst.NS_DC,
+ "title",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_XMP_RIGHTS,
+ "Copyright",
+ XMPConst.NS_DC,
+ "rights",
+ null
+ )
+
+ // Aliases from PDF to DC and XMP.
+ registerAlias(
+ XMPConst.NS_PDF,
+ "Author",
+ XMPConst.NS_DC,
+ "creator",
+ aliasToArrayOrdered
+ )
+ registerAlias(
+ XMPConst.NS_PDF,
+ "BaseURL",
+ XMPConst.NS_XMP,
+ "BaseURL",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PDF,
+ "CreationDate",
+ XMPConst.NS_XMP,
+ "CreateDate",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PDF,
+ "Creator",
+ XMPConst.NS_XMP,
+ "CreatorTool",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PDF,
+ "ModDate",
+ XMPConst.NS_XMP,
+ "ModifyDate",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PDF,
+ "Subject",
+ XMPConst.NS_DC,
+ "description",
+ aliasToArrayAltText
+ )
+ registerAlias(
+ XMPConst.NS_PDF,
+ "Title",
+ XMPConst.NS_DC,
+ "title",
+ aliasToArrayAltText
+ )
+
+ // Aliases from PHOTOSHOP to DC and XMP.
+ registerAlias(
+ XMPConst.NS_PHOTOSHOP,
+ "Author",
+ XMPConst.NS_DC,
+ "creator",
+ aliasToArrayOrdered
+ )
+ registerAlias(
+ XMPConst.NS_PHOTOSHOP,
+ "Caption",
+ XMPConst.NS_DC,
+ "description",
+ aliasToArrayAltText
+ )
+ registerAlias(
+ XMPConst.NS_PHOTOSHOP,
+ "Copyright",
+ XMPConst.NS_DC,
+ "rights",
+ aliasToArrayAltText
+ )
+ registerAlias(
+ XMPConst.NS_PHOTOSHOP,
+ "Keywords",
+ XMPConst.NS_DC,
+ "subject",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PHOTOSHOP,
+ "Marked",
+ XMPConst.NS_XMP_RIGHTS,
+ "Marked",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PHOTOSHOP,
+ "Title",
+ XMPConst.NS_DC,
+ "title",
+ aliasToArrayAltText
+ )
+ registerAlias(
+ XMPConst.NS_PHOTOSHOP,
+ "WebStatement",
+ XMPConst.NS_XMP_RIGHTS,
+ "WebStatement",
+ null
+ )
+
+ // Aliases from TIFF and EXIF to DC and XMP.
+ registerAlias(
+ XMPConst.NS_TIFF,
+ "Artist",
+ XMPConst.NS_DC,
+ "creator",
+ aliasToArrayOrdered
+ )
+ registerAlias(
+ XMPConst.NS_TIFF,
+ "Copyright",
+ XMPConst.NS_DC,
+ "rights",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_TIFF,
+ "DateTime",
+ XMPConst.NS_XMP,
+ "ModifyDate",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_EXIF,
+ "DateTimeDigitized",
+ XMPConst.NS_XMP,
+ "CreateDate",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_TIFF,
+ "ImageDescription",
+ XMPConst.NS_DC,
+ "description",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_TIFF,
+ "Software",
+ XMPConst.NS_XMP,
+ "CreatorTool",
+ null
+ )
+
+ // Aliases from PNG (Acrobat ImageCapture) to DC and XMP.
+ registerAlias(
+ XMPConst.NS_PNG,
+ "Author",
+ XMPConst.NS_DC,
+ "creator",
+ aliasToArrayOrdered
+ )
+ registerAlias(
+ XMPConst.NS_PNG,
+ "Copyright",
+ XMPConst.NS_DC,
+ "rights",
+ aliasToArrayAltText
+ )
+ registerAlias(
+ XMPConst.NS_PNG,
+ "CreationTime",
+ XMPConst.NS_XMP,
+ "CreateDate",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PNG,
+ "Description",
+ XMPConst.NS_DC,
+ "description",
+ aliasToArrayAltText
+ )
+ registerAlias(
+ XMPConst.NS_PNG,
+ "ModificationTime",
+ XMPConst.NS_XMP,
+ "ModifyDate",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PNG,
+ "Software",
+ XMPConst.NS_XMP,
+ "CreatorTool",
+ null
+ )
+ registerAlias(
+ XMPConst.NS_PNG,
+ "Title",
+ XMPConst.NS_DC,
+ "title",
+ aliasToArrayAltText
+ )
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/XmlUtilDomParser.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XmlUtilDomParser.kt
new file mode 100644
index 0000000..cf2ee0f
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/XmlUtilDomParser.kt
@@ -0,0 +1,32 @@
+package com.ashampoo.xmp.impl
+
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import nl.adaptivity.xmlutil.DomWriter
+import nl.adaptivity.xmlutil.EventType
+import nl.adaptivity.xmlutil.XmlStreaming
+import nl.adaptivity.xmlutil.dom.Document
+import nl.adaptivity.xmlutil.writeCurrent
+
+object XmlUtilDomParser : DomParser {
+
+ override fun parseDocumentFromString(input: String): Document {
+
+ try {
+
+ val writer = DomWriter()
+
+ val reader = XmlStreaming.newReader(input)
+
+ do {
+ val event = reader.next()
+ reader.writeCurrent(writer)
+ } while (event != EventType.END_DOCUMENT)
+
+ return writer.target
+
+ } catch (ex: Exception) {
+ throw XMPException("Error reading the XML-file", XMPError.BADSTREAM, ex)
+ }
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/PathPosition.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/PathPosition.kt
new file mode 100644
index 0000000..fbd9a5d
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/PathPosition.kt
@@ -0,0 +1,41 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl.xpath
+
+/**
+ * This objects contains all needed char positions to parse.
+ */
+internal class PathPosition {
+
+ /**
+ * the complete path
+ */
+ var path: String? = null
+
+ /**
+ * the start of a segment name
+ */
+ var nameStart = 0
+
+ /**
+ * the end of a segment name
+ */
+ var nameEnd = 0
+
+ /**
+ * the begin of a step
+ */
+ var stepBegin = 0
+
+ /**
+ * the end of a step
+ */
+ var stepEnd = 0
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPath.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPath.kt
new file mode 100644
index 0000000..4c1c502
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPath.kt
@@ -0,0 +1,79 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl.xpath
+
+/**
+ * Representates an XMP XMPPath with segment accessor methods.
+ */
+class XMPPath {
+
+ private val segments = mutableListOf()
+
+ fun add(segment: XMPPathSegment) {
+ segments.add(segment)
+ }
+
+ fun getSegment(index: Int): XMPPathSegment = segments[index]
+
+ fun size(): Int = segments.size
+
+ override fun toString(): String {
+
+ val result = StringBuilder()
+ var index = 1
+
+ while (index < size()) {
+
+ result.append(getSegment(index))
+
+ if (index < size() - 1) {
+
+ val kind = getSegment(index + 1).kind
+
+ if (kind == STRUCT_FIELD_STEP || kind == QUALIFIER_STEP)
+ result.append('/')
+ }
+
+ index++
+ }
+
+ return result.toString()
+ }
+
+ companion object {
+
+ /**
+ * Marks a struct field step , also for top level nodes (schema "fields").
+ */
+ const val STRUCT_FIELD_STEP = 0x01
+
+ /**
+ * Marks a qualifier step.
+ * Note: Order is significant to separate struct/qual from array kinds!
+ */
+ const val QUALIFIER_STEP = 0x02
+
+ /**
+ * Marks an array index step
+ */
+ const val ARRAY_INDEX_STEP = 0x03
+
+ const val ARRAY_LAST_STEP = 0x04
+
+ const val QUAL_SELECTOR_STEP = 0x05
+
+ const val FIELD_SELECTOR_STEP = 0x06
+
+ const val SCHEMA_NODE = -0x80000000
+
+ const val STEP_SCHEMA = 0
+
+ const val STEP_ROOT_PROP = 1
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPathParser.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPathParser.kt
new file mode 100644
index 0000000..795815f
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPathParser.kt
@@ -0,0 +1,396 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl.xpath
+
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+import com.ashampoo.xmp.XMPMetaFactory.schemaRegistry
+import com.ashampoo.xmp.impl.Utils
+
+/**
+ * Parser for XMP XPaths.
+ */
+object XMPPathParser {
+
+ /**
+ * Split an XMPPath expression apart at the conceptual steps, adding the
+ * root namespace prefix to the first property component. The schema URI is
+ * put in the first (0th) slot in the expanded XMPPath. Check if the top
+ * level component is an alias, but don't resolve it.
+ *
+ * The logic is complicated though by shorthand for arrays, the separating
+ * '/' and leading '*' are optional. These are all equivalent: array/ *[2]
+ * array/[2] array*[2] array[2] All of these are broken into the 2 steps
+ * "array" and "[2]".
+ *
+ * The value portion in the array selector forms is a string quoted by '''
+ * or '"'. The value may contain any character including a doubled quoting
+ * character. The value may be empty.
+ *
+ * The syntax isn't checked, but an XML name begins with a letter or '_',
+ * and contains letters, digits, '.', '-', '_', and a bunch of special
+ * non-ASCII Unicode characters. An XML qualified name is a pair of names
+ * separated by a colon.
+ */
+ @kotlin.jvm.JvmStatic
+ fun expandXPath(schemaNS: String?, path: String?): XMPPath {
+
+ if (schemaNS == null || path == null)
+ throw XMPException("Parameter must not be null", XMPError.BADPARAM)
+
+ val expandedXPath = XMPPath()
+
+ val pos = PathPosition()
+
+ pos.path = path
+
+ // Pull out the first component and do some special processing on it: add the schema
+ // namespace prefix and and see if it is an alias. The start must be a "qualName".
+ parseRootNode(schemaNS, pos, expandedXPath)
+
+ // Now continue to process the rest of the XMPPath string.
+ while (pos.stepEnd < path.length) {
+
+ pos.stepBegin = pos.stepEnd
+
+ skipPathDelimiter(path, pos)
+
+ pos.stepEnd = pos.stepBegin
+
+ var segment: XMPPathSegment
+
+ segment = if (path[pos.stepBegin] != '[') {
+ // A struct field or qualifier.
+ parseStructSegment(pos)
+ } else {
+ // One of the array forms.
+ parseIndexSegment(pos)
+ }
+
+ if (segment.kind == XMPPath.STRUCT_FIELD_STEP) {
+
+ if (segment.name!![0] == '@') {
+
+ segment.name = "?" + segment.name!!.substring(1)
+
+ if ("?xml:lang" != segment.name)
+ throw XMPException("Only xml:lang allowed with '@'", XMPError.BADXPATH)
+ }
+
+ if (segment.name!![0] == '?') {
+
+ pos.nameStart++
+
+ segment.kind = XMPPath.QUALIFIER_STEP
+ }
+
+ verifyQualName(pos.path!!.substring(pos.nameStart, pos.nameEnd))
+
+ } else if (segment.kind == XMPPath.FIELD_SELECTOR_STEP) {
+
+ if (segment.name!![1] == '@') {
+
+ segment.name = "[?" + segment.name!!.substring(2)
+
+ if (!segment.name!!.startsWith("[?xml:lang="))
+ throw XMPException("Only xml:lang allowed with '@'", XMPError.BADXPATH)
+ }
+
+ if (segment.name!![1] == '?') {
+
+ pos.nameStart++
+
+ segment.kind = XMPPath.QUAL_SELECTOR_STEP
+
+ verifyQualName(pos.path!!.substring(pos.nameStart, pos.nameEnd))
+ }
+ }
+
+ expandedXPath.add(segment)
+ }
+
+ return expandedXPath
+ }
+
+ private fun skipPathDelimiter(path: String, pos: PathPosition) {
+
+ if (path[pos.stepBegin] == '/') {
+
+ // skip slash
+ pos.stepBegin++
+
+ if (pos.stepBegin >= path.length)
+ throw XMPException("Empty XMPPath segment", XMPError.BADXPATH)
+ }
+
+ if (path[pos.stepBegin] == '*') {
+
+ // skip asterisk
+ pos.stepBegin++
+
+ if (pos.stepBegin >= path.length || path[pos.stepBegin] != '[')
+ throw XMPException("Missing '[' after '*'", XMPError.BADXPATH)
+ }
+ }
+
+ private fun parseStructSegment(pos: PathPosition): XMPPathSegment {
+
+ pos.nameStart = pos.stepBegin
+
+ while (pos.stepEnd < pos.path!!.length && "/[*".indexOf(pos.path!![pos.stepEnd]) < 0)
+ pos.stepEnd++
+
+ pos.nameEnd = pos.stepEnd
+
+ if (pos.stepEnd == pos.stepBegin)
+ throw XMPException("Empty XMPPath segment", XMPError.BADXPATH)
+
+ return XMPPathSegment(
+ pos.path!!.substring(pos.stepBegin, pos.stepEnd),
+ XMPPath.STRUCT_FIELD_STEP
+ )
+ }
+
+ /**
+ * Parses an array index segment.
+ */
+ private fun parseIndexSegment(pos: PathPosition): XMPPathSegment {
+
+ val segment: XMPPathSegment
+
+ pos.stepEnd++ // Look at the character after the leading '['.
+
+ if ('0' <= pos.path!![pos.stepEnd] && pos.path!![pos.stepEnd] <= '9') {
+
+ // A numeric (decimal integer) array index.
+ while (
+ pos.stepEnd < pos.path!!.length &&
+ '0' <= pos.path!![pos.stepEnd] && pos.path!![pos.stepEnd] <= '9'
+ )
+ pos.stepEnd++
+
+ segment = XMPPathSegment(null, XMPPath.ARRAY_INDEX_STEP)
+
+ } else {
+
+ // Could be "[last()]" or one of the selector forms. Find the ']' or '='.
+ while (
+ pos.stepEnd < pos.path!!.length && pos.path!![pos.stepEnd] != ']' &&
+ pos.path!![pos.stepEnd] != '='
+ )
+ pos.stepEnd++
+
+ if (pos.stepEnd >= pos.path!!.length)
+ throw XMPException("Missing ']' or '=' for array index", XMPError.BADXPATH)
+
+ if (pos.path!![pos.stepEnd] == ']') {
+
+ if ("[last()" != pos.path!!.substring(pos.stepBegin, pos.stepEnd))
+ throw XMPException("Invalid non-numeric array index", XMPError.BADXPATH)
+
+ segment = XMPPathSegment(null, XMPPath.ARRAY_LAST_STEP)
+
+ } else {
+
+ pos.nameStart = pos.stepBegin + 1
+ pos.nameEnd = pos.stepEnd
+
+ pos.stepEnd++ // Absorb the '=', remember the quote.
+
+ val quote = pos.path!![pos.stepEnd]
+
+ if (quote != '\'' && quote != '"')
+ throw XMPException("Invalid quote in array selector", XMPError.BADXPATH)
+
+ pos.stepEnd++ // Absorb the leading quote.
+
+ while (pos.stepEnd < pos.path!!.length) {
+
+ if (pos.path!![pos.stepEnd] == quote) {
+
+ // check for escaped quote
+ if (pos.stepEnd + 1 >= pos.path!!.length || pos.path!![pos.stepEnd + 1] != quote)
+ break
+
+ pos.stepEnd++
+ }
+
+ pos.stepEnd++
+ }
+
+ if (pos.stepEnd >= pos.path!!.length)
+ throw XMPException("No terminating quote for array selector", XMPError.BADXPATH)
+
+ pos.stepEnd++ // Absorb the trailing quote.
+
+ // ! Touch up later, also changing '@' to '?'.
+ segment = XMPPathSegment(null, XMPPath.FIELD_SELECTOR_STEP)
+ }
+ }
+
+ if (pos.stepEnd >= pos.path!!.length || pos.path!![pos.stepEnd] != ']')
+ throw XMPException("Missing ']' for array index", XMPError.BADXPATH)
+
+ pos.stepEnd++
+
+ segment.name = pos.path!!.substring(pos.stepBegin, pos.stepEnd)
+
+ return segment
+ }
+
+ /**
+ * Parses the root node of an XMP Path, checks if namespace and prefix fit together
+ * and resolve the property to the base property if it is an alias.
+ */
+ private fun parseRootNode(schemaNS: String, pos: PathPosition, expandedXPath: XMPPath) {
+
+ while (pos.stepEnd < pos.path!!.length && "/[*".indexOf(pos.path!![pos.stepEnd]) < 0)
+ pos.stepEnd++
+
+ if (pos.stepEnd == pos.stepBegin)
+ throw XMPException("Empty initial XMPPath step", XMPError.BADXPATH)
+
+ val rootProp = verifyXPathRoot(schemaNS, pos.path!!.substring(pos.stepBegin, pos.stepEnd))
+ val aliasInfo = schemaRegistry.findAlias(rootProp)
+
+ if (aliasInfo == null) {
+
+ // add schema xpath step
+ expandedXPath.add(XMPPathSegment(schemaNS, XMPPath.SCHEMA_NODE))
+
+ val rootStep = XMPPathSegment(rootProp, XMPPath.STRUCT_FIELD_STEP)
+
+ expandedXPath.add(rootStep)
+
+ } else {
+
+ // add schema xpath step and base step of alias
+ expandedXPath.add(XMPPathSegment(aliasInfo.getNamespace(), XMPPath.SCHEMA_NODE))
+
+ val rootStep = XMPPathSegment(
+ verifyXPathRoot(aliasInfo.getNamespace(), aliasInfo.getPropName()),
+ XMPPath.STRUCT_FIELD_STEP
+ )
+
+ rootStep.isAlias = true
+ rootStep.aliasForm = aliasInfo.getAliasForm().getOptions()
+
+ expandedXPath.add(rootStep)
+
+ if (aliasInfo.getAliasForm().isArrayAltText()) {
+
+ val qualSelectorStep =
+ XMPPathSegment("[?xml:lang='x-default']", XMPPath.QUAL_SELECTOR_STEP)
+
+ qualSelectorStep.isAlias = true
+ qualSelectorStep.aliasForm = aliasInfo.getAliasForm().getOptions()
+
+ expandedXPath.add(qualSelectorStep)
+
+ } else if (aliasInfo.getAliasForm().isArray()) {
+
+ val indexStep = XMPPathSegment("[1]", XMPPath.ARRAY_INDEX_STEP)
+
+ indexStep.isAlias = true
+ indexStep.aliasForm = aliasInfo.getAliasForm().getOptions()
+
+ expandedXPath.add(indexStep)
+ }
+ }
+ }
+
+ /**
+ * Verifies whether the qualifier name is not XML conformant or the
+ * namespace prefix has not been registered.
+ */
+ private fun verifyQualName(qualName: String) {
+
+ val colonPos = qualName.indexOf(':')
+
+ if (colonPos > 0) {
+
+ val prefix = qualName.substring(0, colonPos)
+
+ if (Utils.isXMLNameNS(prefix)) {
+
+ val regURI = schemaRegistry.getNamespaceURI(prefix)
+
+ if (regURI != null)
+ return
+
+ throw XMPException("Unknown namespace prefix for qualified name", XMPError.BADXPATH)
+ }
+ }
+
+ throw XMPException("Ill-formed qualified name", XMPError.BADXPATH)
+ }
+
+ /**
+ * Verify if an XML name is conformant.
+ */
+ private fun verifySimpleXMLName(name: String) {
+
+ if (!Utils.isXMLName(name))
+ throw XMPException("Bad XML name", XMPError.BADXPATH)
+ }
+
+ /**
+ * Set up the first 2 components of the expanded XMPPath. Normalizes the various cases of using
+ * the full schema URI and/or a qualified root property name. Returns true for normal
+ * processing. If allowUnknownSchemaNS is true and the schema namespace is not registered, false
+ * is returned. If allowUnknownSchemaNS is false and the schema namespace is not registered, an
+ * exception is thrown
+ */
+ private fun verifyXPathRoot(schemaNS: String?, rootProp: String): String {
+
+ // Do some basic checks on the URI and name. Try to lookup the URI. See if the name is qualified.
+ if (schemaNS == null || schemaNS.length == 0)
+ throw XMPException("Schema namespace URI is required", XMPError.BADSCHEMA)
+
+ if (rootProp[0] == '?' || rootProp[0] == '@')
+ throw XMPException("Top level name must not be a qualifier", XMPError.BADXPATH)
+
+ if (rootProp.indexOf('/') >= 0 || rootProp.indexOf('[') >= 0)
+ throw XMPException("Top level name must be simple", XMPError.BADXPATH)
+
+ var prefix = schemaRegistry.getNamespacePrefix(schemaNS)
+ ?: throw XMPException("Unregistered schema namespace URI", XMPError.BADSCHEMA)
+
+ // Verify the various URI and prefix combinations. Initialize the expanded XMPPath.
+ val colonPos = rootProp.indexOf(':')
+
+ return if (colonPos < 0) {
+
+ // The propName is unqualified, use the schemaURI and associated prefix.
+
+ verifySimpleXMLName(rootProp) // Verify the part before any colon
+
+ prefix + rootProp
+
+ } else {
+
+ // The propName is qualified. Make sure the prefix is legit.
+ // Use the associated URI and qualified name.
+
+ // Verify the part before any colon
+ verifySimpleXMLName(rootProp.substring(0, colonPos))
+ verifySimpleXMLName(rootProp.substring(colonPos))
+
+ prefix = rootProp.substring(0, colonPos + 1)
+
+ val regPrefix = schemaRegistry.getNamespacePrefix(schemaNS)
+ ?: throw XMPException("Unknown schema namespace prefix", XMPError.BADSCHEMA)
+
+ if (prefix != regPrefix)
+ throw XMPException("Schema namespace URI and prefix mismatch", XMPError.BADSCHEMA)
+
+ rootProp
+ }
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPathSegment.kt b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPathSegment.kt
new file mode 100644
index 0000000..f63c420
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/impl/xpath/XMPPathSegment.kt
@@ -0,0 +1,56 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.impl.xpath
+
+/**
+ * A segment of a parsed `XMPPath`.
+ */
+class XMPPathSegment {
+
+ /**
+ * name of the path segment
+ */
+ var name: String?
+
+ /**
+ * kind of the path segment
+ */
+ var kind = 0
+
+ /**
+ * flag if segment is an alias
+ */
+ var isAlias = false
+
+ /**
+ * alias form if applicable
+ */
+ var aliasForm = 0
+
+ /**
+ * Constructor with initial values.
+ */
+ constructor(name: String) {
+ this.name = name
+ }
+
+ /**
+ * Constructor with initial values.
+ *
+ * Note: Name can be NULL for XMPPath.ARRAY_INDEX_STEP and others.
+ */
+ constructor(name: String?, kind: Int) {
+ this.name = name
+ this.kind = kind
+ }
+
+ override fun toString(): String =
+ name ?: "null"
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/options/AliasOptions.kt b/src/commonMain/kotlin/com/ashampoo/xmp/options/AliasOptions.kt
new file mode 100644
index 0000000..21df954
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/options/AliasOptions.kt
@@ -0,0 +1,96 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.options
+
+/**
+ * Options for XMPSchemaRegistryImpl#registerAlias.
+ */
+class AliasOptions : Options {
+
+ constructor() : super()
+
+ constructor(options: Int) : super(options)
+
+ fun isSimple(): Boolean =
+ getOptions() == PROP_DIRECT
+
+ fun isArray(): Boolean =
+ getOption(PROP_ARRAY)
+
+ fun setArray(value: Boolean): AliasOptions {
+ setOption(PROP_ARRAY, value)
+ return this
+ }
+
+ fun isArrayOrdered(): Boolean =
+ getOption(PROP_ARRAY_ORDERED)
+
+ fun setArrayOrdered(value: Boolean): AliasOptions {
+ setOption(PROP_ARRAY or PROP_ARRAY_ORDERED, value)
+ return this
+ }
+
+ fun isArrayAlternate(): Boolean =
+ getOption(PROP_ARRAY_ALTERNATE)
+
+ fun setArrayAlternate(value: Boolean): AliasOptions {
+ setOption(PROP_ARRAY or PROP_ARRAY_ORDERED or PROP_ARRAY_ALTERNATE, value)
+ return this
+ }
+
+ fun isArrayAltText(): Boolean =
+ getOption(PROP_ARRAY_ALT_TEXT)
+
+ fun setArrayAltText(value: Boolean): AliasOptions {
+ setOption(PROP_ARRAY or PROP_ARRAY_ORDERED or PROP_ARRAY_ALTERNATE or PROP_ARRAY_ALT_TEXT, value)
+ return this
+ }
+
+ fun toPropertyOptions(): PropertyOptions =
+ PropertyOptions(getOptions())
+
+ protected override fun defineOptionName(option: Int): String? {
+ return when (option) {
+ PROP_DIRECT -> "PROP_DIRECT"
+ PROP_ARRAY -> "ARRAY"
+ PROP_ARRAY_ORDERED -> "ARRAY_ORDERED"
+ PROP_ARRAY_ALTERNATE -> "ARRAY_ALTERNATE"
+ PROP_ARRAY_ALT_TEXT -> "ARRAY_ALT_TEXT"
+ else -> null
+ }
+ }
+
+ protected override fun getValidOptions(): Int =
+ PROP_DIRECT or PROP_ARRAY or PROP_ARRAY_ORDERED or PROP_ARRAY_ALTERNATE or PROP_ARRAY_ALT_TEXT
+
+ companion object {
+
+ const val PROP_DIRECT = 0
+
+ /**
+ * The actual is an unordered array, the alias is to the first element of the array.
+ */
+ const val PROP_ARRAY = PropertyOptions.ARRAY
+
+ /**
+ * The actual is an ordered array, the alias is to the first element of the array.
+ */
+ const val PROP_ARRAY_ORDERED = PropertyOptions.ARRAY_ORDERED
+
+ /**
+ * The actual is an alternate array, the alias is to the first element of the array.
+ */
+ const val PROP_ARRAY_ALTERNATE = PropertyOptions.ARRAY_ALTERNATE
+
+ /**
+ * The actual is an alternate text array, the alias is to the 'x-default' element of the array.
+ */
+ const val PROP_ARRAY_ALT_TEXT = PropertyOptions.ARRAY_ALT_TEXT
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/options/IteratorOptions.kt b/src/commonMain/kotlin/com/ashampoo/xmp/options/IteratorOptions.kt
new file mode 100644
index 0000000..254062e
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/options/IteratorOptions.kt
@@ -0,0 +1,127 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.options
+
+/**
+ * Options for XMPIterator construction.
+ */
+class IteratorOptions : Options() {
+
+ /**
+ * @return Returns whether the option is set.
+ */
+ fun isJustChildren(): Boolean =
+ getOption(JUST_CHILDREN)
+
+ /**
+ * @return Returns whether the option is set.
+ */
+ fun isJustLeafname(): Boolean =
+ getOption(JUST_LEAFNAME)
+
+ /**
+ * @return Returns whether the option is set.
+ */
+ fun isJustLeafnodes(): Boolean =
+ getOption(JUST_LEAFNODES)
+
+ /**
+ * @return Returns whether the option is set.
+ */
+ fun isOmitQualifiers(): Boolean =
+ getOption(OMIT_QUALIFIERS)
+
+ /**
+ * Sets the option and returns the instance.
+ *
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setJustChildren(value: Boolean): IteratorOptions {
+ setOption(JUST_CHILDREN, value)
+ return this
+ }
+
+ /**
+ * Sets the option and returns the instance.
+ *
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setJustLeafname(value: Boolean): IteratorOptions {
+ setOption(JUST_LEAFNAME, value)
+ return this
+ }
+
+ /**
+ * Sets the option and returns the instance.
+ *
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setJustLeafnodes(value: Boolean): IteratorOptions {
+ setOption(JUST_LEAFNODES, value)
+ return this
+ }
+
+ /**
+ * Sets the option and returns the instance.
+ *
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setOmitQualifiers(value: Boolean): IteratorOptions {
+ setOption(OMIT_QUALIFIERS, value)
+ return this
+ }
+
+ /**
+ * @see Options.defineOptionName
+ */
+ override fun defineOptionName(option: Int): String? {
+ return when (option) {
+ JUST_CHILDREN -> "JUST_CHILDREN"
+ JUST_LEAFNODES -> "JUST_LEAFNODES"
+ JUST_LEAFNAME -> "JUST_LEAFNAME"
+ OMIT_QUALIFIERS -> "OMIT_QUALIFIERS"
+ else -> null
+ }
+ }
+
+ /**
+ * @see Options.getValidOptions
+ */
+ override fun getValidOptions(): Int =
+ JUST_CHILDREN or JUST_LEAFNODES or JUST_LEAFNAME or OMIT_QUALIFIERS
+
+ /**
+ * Just do the immediate children of the root, default is subtree.
+ */
+ companion object {
+
+ const val JUST_CHILDREN = 0x0100
+
+ /**
+ * Just do the leaf nodes, default is all nodes in the subtree.
+ * Bugfix #2658965: If this option is set the Iterator returns the namespace
+ * of the leaf instead of the namespace of the base property.
+ */
+ const val JUST_LEAFNODES = 0x0200
+
+ /**
+ * Return just the leaf part of the path, default is the full path.
+ */
+ const val JUST_LEAFNAME = 0x0400
+
+ /**
+ * Omit all qualifiers.
+ */
+ const val OMIT_QUALIFIERS = 0x1000
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/options/Options.kt b/src/commonMain/kotlin/com/ashampoo/xmp/options/Options.kt
new file mode 100644
index 0000000..19b3855
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/options/Options.kt
@@ -0,0 +1,229 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.options
+
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+
+/**
+ * The base class for a collection of 32 flag bits. Individual flags are defined as enum value bit
+ * masks. Inheriting classes add convenience accessor methods.
+ */
+abstract class Options {
+
+ /**
+ * the internal int containing all options
+ */
+ private var valueBits = 0
+
+ /**
+ * a map containing the bit names
+ */
+ private val optionNames = mutableMapOf()
+
+ /**
+ * The default constructor.
+ */
+ protected constructor()
+
+ /**
+ * Constructor with the options bit mask.
+ *
+ * @param options the options bit mask
+ *
+ */
+ protected constructor(options: Int) {
+ assertOptionsValid(options)
+ setOptions(options)
+ }
+
+ protected abstract fun getValidOptions(): Int
+
+ /**
+ * Resets the options.
+ */
+ fun clear() {
+ valueBits = 0
+ }
+
+ /**
+ * @param optionBits an option bitmask
+ * @return Returns true, if this object is equal to the given options.
+ */
+ fun isExactly(optionBits: Int): Boolean =
+ getOptions() == optionBits
+
+ /**
+ * @param optionBits an option bitmask
+ * @return Returns true, if this object contains all given options.
+ */
+ fun containsAllOptions(optionBits: Int): Boolean =
+ getOptions() and optionBits == optionBits
+
+ /**
+ * @param optionBits an option bitmask
+ * @return Returns true, if this object contain at least one of the given options.
+ */
+ fun containsOneOf(optionBits: Int): Boolean =
+ getOptions() and optionBits != 0
+
+ /**
+ * @param optionBit the binary bit or bits that are requested
+ * @return Returns if *all* of the requested bits are set or not.
+ */
+ protected fun getOption(optionBit: Int): Boolean =
+ valueBits and optionBit != 0
+
+ /**
+ * @param optionBits the binary bit or bits that shall be set to the given value
+ * @param value the boolean value to set
+ */
+ fun setOption(optionBits: Int, value: Boolean) {
+ this.valueBits = if (value)
+ this.valueBits or optionBits
+ else
+ this.valueBits and optionBits.inv()
+ }
+
+ /**
+ * Is friendly to access it during the tests.
+ *
+ * @return Returns the options.
+ */
+ fun getOptions(): Int = valueBits
+
+ /**
+ * @param options The options to set.
+ *
+ */
+ fun setOptions(options: Int) {
+
+ assertOptionsValid(options)
+
+ this.valueBits = options
+ }
+
+ /**
+ * @see Object.equals
+ */
+ override fun equals(other: Any?): Boolean =
+ getOptions() == (other as? Options)?.getOptions()
+
+ /**
+ * @see Object.hashCode
+ */
+ override fun hashCode(): Int = getOptions()
+
+ /**
+ * Creates a human readable string from the set options. *Note:* This method is quite
+ * expensive and should only be used within tests or as
+ *
+ * @return Returns a String listing all options that are set to `true` by their name,
+ * like "option1 | option4".
+ */
+ fun getOptionsString(): String {
+
+ if (valueBits != 0) {
+
+ val sb = StringBuilder()
+
+ var theBits = valueBits
+
+ while (theBits != 0) {
+
+ val oneLessBit = theBits and theBits - 1 // clear rightmost one bit
+ val singleBit = theBits xor oneLessBit
+ val bitName = getOptionName(singleBit)
+ sb.append(bitName)
+
+ if (oneLessBit != 0)
+ sb.append(" | ")
+
+ theBits = oneLessBit
+ }
+
+ return sb.toString()
+
+ } else {
+ return ""
+ }
+ }
+
+ /**
+ * @return Returns the options as hex bitmask.
+ */
+ override fun toString(): String =
+ "0x" + valueBits.toString(16)
+
+ /**
+ * To be implemeted by inheritants.
+ *
+ * @param option a single, valid option bit.
+ * @return Returns a human readable name for an option bit.
+ */
+ protected abstract fun defineOptionName(option: Int): String?
+
+ /**
+ * The inheriting option class can do additional checks on the options.
+ * *Note:* For performance reasons this method is only called
+ * when setting bitmasks directly.
+ * When get- and set-methods are used, this method must be called manually,
+ * normally only when the Options-object has been created from a client
+ * (it has to be made public therefore).
+ *
+ * @param options the bitmask to check.
+ *
+ */
+ protected open fun assertConsistency(options: Int) = Unit // empty, no checks
+
+ /**
+ * Checks options before they are set.
+ * First it is checked if only defined options are used,
+ * second the additional [Options.assertConsistency]-method is called.
+ *
+ * @param options the options to check
+ *
+ */
+ private fun assertOptionsValid(options: Int) {
+
+ val invalidOptions = options and getValidOptions().inv()
+
+ if (invalidOptions == 0)
+ assertConsistency(options)
+ else
+ throw XMPException(
+ "The option bit(s) 0x" + invalidOptions.toString(16) + " + are invalid!",
+ XMPError.BADOPTIONS
+ )
+ }
+
+ /**
+ * Looks up or asks the inherited class for the name of an option bit.
+ * Its save that there is only one valid option handed into the method.
+ *
+ * @param option a single option bit
+ * @return Returns the option name or undefined.
+ */
+ private fun getOptionName(option: Int): String {
+
+ var result = optionNames[option]
+
+ if (result == null) {
+
+ result = defineOptionName(option)
+
+ if (result != null)
+ optionNames[option] = result
+ else
+ result = ""
+ }
+
+ return result
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/options/ParseOptions.kt b/src/commonMain/kotlin/com/ashampoo/xmp/options/ParseOptions.kt
new file mode 100644
index 0000000..5bfa40b
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/options/ParseOptions.kt
@@ -0,0 +1,96 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.options
+
+/**
+ * Options for [XMPMetaFactory.parse].
+ */
+class ParseOptions : Options() {
+
+ /**
+ * @return Returns the requireXMPMeta.
+ */
+ fun getRequireXMPMeta(): Boolean =
+ getOption(REQUIRE_XMP_META)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setRequireXMPMeta(value: Boolean): ParseOptions {
+ setOption(REQUIRE_XMP_META, value)
+ return this
+ }
+
+ /**
+ * @return Returns the strictAliasing.
+ */
+ fun getStrictAliasing(): Boolean =
+ getOption(STRICT_ALIASING)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setStrictAliasing(value: Boolean): ParseOptions {
+ setOption(STRICT_ALIASING, value)
+ return this
+ }
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setOmitNormalization(value: Boolean): ParseOptions {
+ setOption(OMIT_NORMALIZATION, value)
+ return this
+ }
+
+ /**
+ * @return Returns the option "omit normalization".
+ */
+ fun getOmitNormalization(): Boolean =
+ getOption(OMIT_NORMALIZATION)
+
+ /**
+ * @see Options.defineOptionName
+ */
+ override fun defineOptionName(option: Int): String? {
+ return when (option) {
+ REQUIRE_XMP_META -> "REQUIRE_XMP_META"
+ STRICT_ALIASING -> "STRICT_ALIASING"
+ OMIT_NORMALIZATION -> "OMIT_NORMALIZATION"
+ else -> null
+ }
+ }
+
+ /**
+ * @see Options.getValidOptions
+ */
+ override fun getValidOptions(): Int =
+ REQUIRE_XMP_META or STRICT_ALIASING or OMIT_NORMALIZATION
+
+ /**
+ * Require a surrounding "x:xmpmeta" element in the xml-document.
+ */
+ companion object {
+
+ const val REQUIRE_XMP_META = 0x0001
+
+ /**
+ * Do not reconcile alias differences, throw an exception instead.
+ */
+ const val STRICT_ALIASING = 0x0004
+
+ /**
+ * Do not carry run the XMPNormalizer on a packet, leave it as it is.
+ */
+ const val OMIT_NORMALIZATION = 0x0020
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/options/PropertyOptions.kt b/src/commonMain/kotlin/com/ashampoo/xmp/options/PropertyOptions.kt
new file mode 100644
index 0000000..b5d8e90
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/options/PropertyOptions.kt
@@ -0,0 +1,357 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.options
+
+import com.ashampoo.xmp.XMPError
+import com.ashampoo.xmp.XMPException
+
+/**
+ * The property flags are used when properties are fetched from the `XMPMeta`-object
+ * and provide more detailed information about the property.
+ */
+class PropertyOptions : Options {
+
+ /**
+ * Default constructor
+ */
+ constructor()
+
+ /**
+ * Intialization constructor
+ *
+ * @param options the initialization options
+ *
+ */
+ constructor(options: Int) : super(options)
+
+ /**
+ * @return Return whether the property value is a URI. It is serialized to RDF using the
+ * rdf:resource attribute. Not mandatory for URIs, but considered RDF-savvy.
+ */
+ fun isURI(): Boolean = getOption(URI)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setURI(value: Boolean): PropertyOptions {
+ setOption(URI, value)
+ return this
+ }
+
+ /**
+ * @return Return whether the property has qualifiers. These could be an xml:lang
+ * attribute, an rdf:type property, or a general qualifier. See the
+ * introductory discussion of qualified properties for more information.
+ */
+ fun hasQualifiers(): Boolean =
+ getOption(HAS_QUALIFIERS)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setHasQualifiers(value: Boolean): PropertyOptions {
+ setOption(HAS_QUALIFIERS, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property is a qualifier for some other property. Note that if the
+ * qualifier itself has a structured value, this flag is only set for the top node of
+ * the qualifier's subtree. Qualifiers may have arbitrary structure, and may even have
+ * qualifiers.
+ */
+ fun isQualifier(): Boolean =
+ getOption(QUALIFIER)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setQualifier(value: Boolean): PropertyOptions {
+ setOption(QUALIFIER, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property has an xml:lang qualifier.
+ */
+ fun hasLanguage(): Boolean =
+ getOption(HAS_LANGUAGE)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setHasLanguage(value: Boolean): PropertyOptions {
+ setOption(HAS_LANGUAGE, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property has an rdf:type qualifier.
+ */
+ fun hasType(): Boolean =
+ getOption(HAS_TYPE)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setHasType(value: Boolean): PropertyOptions {
+ setOption(HAS_TYPE, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property contains nested fields.
+ */
+ fun isStruct(): Boolean =
+ getOption(STRUCT)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setStruct(value: Boolean): PropertyOptions {
+ setOption(STRUCT, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property is an array. By itself this indicates a general
+ * unordered array. It is serialized using an rdf:Bag container.
+ */
+ fun isArray(): Boolean =
+ getOption(ARRAY)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setArray(value: Boolean): PropertyOptions {
+ setOption(ARRAY, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property is an ordered array. Appears in conjunction with
+ * getPropValueIsArray(). It is serialized using an rdf:Seq container.
+ */
+ fun isArrayOrdered(): Boolean =
+ getOption(ARRAY_ORDERED)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setArrayOrdered(value: Boolean): PropertyOptions {
+ setOption(ARRAY_ORDERED, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property is an alternative array. Appears in conjunction with
+ * getPropValueIsArray(). It is serialized using an rdf:Alt container.
+ */
+ fun isArrayAlternate(): Boolean =
+ getOption(ARRAY_ALTERNATE)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setArrayAlternate(value: Boolean): PropertyOptions {
+ setOption(ARRAY_ALTERNATE, value)
+ return this
+ }
+
+ /**
+ * @return Return whether this property is an alt-text array. Appears in conjunction with
+ * getPropArrayIsAlternate(). It is serialized using an rdf:Alt container.
+ * Each array element is a simple property with an xml:lang attribute.
+ */
+ fun isArrayAltText(): Boolean =
+ getOption(ARRAY_ALT_TEXT)
+
+ /**
+ * @param value the value to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setArrayAltText(value: Boolean): PropertyOptions {
+ setOption(ARRAY_ALT_TEXT, value)
+ return this
+ }
+
+ /**
+ * @return Returns whether the SCHEMA_NODE option is set.
+ */
+ fun isSchemaNode(): Boolean =
+ getOption(SCHEMA_NODE)
+
+ /**
+ * @param value the option DELETE_EXISTING to set
+ * @return Returns this to enable cascaded options.
+ */
+ fun setSchemaNode(value: Boolean): PropertyOptions {
+ setOption(SCHEMA_NODE, value)
+ return this
+ }
+
+ /**
+ * @return Returns whether the property is of composite type - an array or a struct.
+ */
+ fun isCompositeProperty(): Boolean =
+ getOptions() and (ARRAY or STRUCT) > 0
+
+ /**
+ * @return Returns whether the property is of composite type - an array or a struct.
+ */
+ fun isSimple(): Boolean =
+ getOptions() and (ARRAY or STRUCT) == 0
+
+ /**
+ * Compares two options set for array compatibility.
+ *
+ * @param options other options
+ * @return Returns true if the array options of the sets are equal.
+ */
+ fun equalArrayTypes(options: PropertyOptions): Boolean =
+ isArray() == options.isArray() &&
+ isArrayOrdered() == options.isArrayOrdered() &&
+ isArrayAlternate() == options.isArrayAlternate() &&
+ isArrayAltText() == options.isArrayAltText()
+
+ /**
+ * Merges the set options of a another options object with this.
+ * If the other options set is null, this objects stays the same.
+ *
+ * @param options other options
+ */
+ fun mergeWith(options: PropertyOptions) {
+ setOptions(getOptions() or options.getOptions())
+ }
+
+ /**
+ * @return Returns true if only array options are set.
+ */
+ fun isOnlyArrayOptions(): Boolean =
+ getOptions() and (ARRAY or ARRAY_ORDERED or ARRAY_ALTERNATE or ARRAY_ALT_TEXT).inv() == 0
+
+ /**
+ * @see Options.getValidOptions
+ */
+ override fun getValidOptions(): Int =
+ URI or HAS_QUALIFIERS or QUALIFIER or HAS_LANGUAGE or HAS_TYPE or STRUCT or ARRAY or
+ ARRAY_ORDERED or ARRAY_ALTERNATE or ARRAY_ALT_TEXT or DELETE_EXISTING or SCHEMA_NODE
+
+ /**
+ * @see Options.defineOptionName
+ */
+ override fun defineOptionName(option: Int): String? {
+ return when (option) {
+ URI -> "URI"
+ HAS_QUALIFIERS -> "HAS_QUALIFIER"
+ QUALIFIER -> "QUALIFIER"
+ HAS_LANGUAGE -> "HAS_LANGUAGE"
+ HAS_TYPE -> "HAS_TYPE"
+ STRUCT -> "STRUCT"
+ ARRAY -> "ARRAY"
+ ARRAY_ORDERED -> "ARRAY_ORDERED"
+ ARRAY_ALTERNATE -> "ARRAY_ALTERNATE"
+ ARRAY_ALT_TEXT -> "ARRAY_ALT_TEXT"
+ SCHEMA_NODE -> "SCHEMA_NODE"
+ else -> null
+ }
+ }
+
+ /**
+ * Checks that a node not a struct and array at the same time;
+ * and URI cannot be a struct.
+ *
+ * @param options the bitmask to check.
+ *
+ */
+ public override fun assertConsistency(options: Int) {
+
+ if (options and STRUCT > 0 && options and ARRAY > 0)
+ throw XMPException("IsStruct and IsArray options are mutually exclusive", XMPError.BADOPTIONS)
+ else if (options and URI > 0 && options and (ARRAY or STRUCT) > 0)
+ throw XMPException("Structs and arrays can't have \"value\" options", XMPError.BADOPTIONS)
+ }
+
+ companion object {
+
+ /**
+ *
+ */
+ const val NO_OPTIONS = 0x00000000
+
+ /**
+ *
+ */
+ const val URI = 0x00000002
+
+ /**
+ *
+ */
+ const val HAS_QUALIFIERS = 0x00000010
+
+ /**
+ *
+ */
+ const val QUALIFIER = 0x00000020
+
+ /**
+ *
+ */
+ const val HAS_LANGUAGE = 0x00000040
+
+ /**
+ *
+ */
+ const val HAS_TYPE = 0x00000080
+
+ /**
+ *
+ */
+ const val STRUCT = 0x00000100
+
+ /**
+ *
+ */
+ const val ARRAY = 0x00000200
+
+ /**
+ *
+ */
+ const val ARRAY_ORDERED = 0x00000400
+
+ /**
+ *
+ */
+ const val ARRAY_ALTERNATE = 0x00000800
+
+ /**
+ *
+ */
+ const val ARRAY_ALT_TEXT = 0x00001000
+
+ /**
+ *
+ */
+ const val SCHEMA_NODE = -0x80000000
+
+ /**
+ * may be used in the future
+ */
+ const val DELETE_EXISTING = 0x20000000
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/options/SerializeOptions.kt b/src/commonMain/kotlin/com/ashampoo/xmp/options/SerializeOptions.kt
new file mode 100644
index 0000000..d814657
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/options/SerializeOptions.kt
@@ -0,0 +1,163 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.options
+
+/**
+ * Options for [XMPMetaFactory.serializeToBuffer].
+ */
+class SerializeOptions : Options {
+
+ /**
+ * Default constructor.
+ */
+ constructor()
+
+ /**
+ * Constructor using inital options
+ *
+ * @param options the inital options
+ *
+ */
+ constructor(options: Int) : super(options)
+
+ fun getOmitPacketWrapper(): Boolean =
+ getOption(OMIT_PACKET_WRAPPER)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setOmitPacketWrapper(value: Boolean): SerializeOptions {
+ setOption(OMIT_PACKET_WRAPPER, value)
+ return this
+ }
+
+ fun getOmitXmpMetaElement(): Boolean =
+ getOption(OMIT_XMPMETA_ELEMENT)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setOmitXmpMetaElement(value: Boolean): SerializeOptions {
+ setOption(OMIT_XMPMETA_ELEMENT, value)
+ return this
+ }
+
+ fun getReadOnlyPacket(): Boolean =
+ getOption(READONLY_PACKET)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setReadOnlyPacket(value: Boolean): SerializeOptions {
+ setOption(READONLY_PACKET, value)
+ return this
+ }
+
+ fun getUseCompactFormat(): Boolean =
+ getOption(USE_COMPACT_FORMAT)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setUseCompactFormat(value: Boolean): SerializeOptions {
+ setOption(USE_COMPACT_FORMAT, value)
+ return this
+ }
+
+ fun getUseCanonicalFormat(): Boolean =
+ getOption(USE_CANONICAL_FORMAT)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setUseCanonicalFormat(value: Boolean): SerializeOptions {
+ setOption(USE_CANONICAL_FORMAT, value)
+ return this
+ }
+
+ fun getSort(): Boolean =
+ getOption(SORT)
+
+ /**
+ * @param value the value to set
+ * @return Returns the instance to call more set-methods.
+ */
+ fun setSort(value: Boolean): SerializeOptions {
+ setOption(SORT, value)
+ return this
+ }
+
+ /**
+ * @return Returns clone of this SerializeOptions-object with the same options set.
+ *
+ */
+ public fun clone(): SerializeOptions =
+ SerializeOptions(getOptions())
+
+ /**
+ * @see Options.defineOptionName
+ */
+ override fun defineOptionName(option: Int): String? {
+ return when (option) {
+ OMIT_PACKET_WRAPPER -> "OMIT_PACKET_WRAPPER"
+ READONLY_PACKET -> "READONLY_PACKET"
+ USE_COMPACT_FORMAT -> "USE_COMPACT_FORMAT"
+ OMIT_XMPMETA_ELEMENT -> "OMIT_XMPMETA_ELEMENT"
+ SORT -> "NORMALIZED"
+ else -> null
+ }
+ }
+
+ /**
+ * @see Options.getValidOptions
+ */
+ override fun getValidOptions(): Int =
+ OMIT_PACKET_WRAPPER or READONLY_PACKET or USE_COMPACT_FORMAT or OMIT_XMPMETA_ELEMENT or SORT
+
+ companion object {
+
+ /**
+ * Omit the XML packet wrapper.
+ */
+ const val OMIT_PACKET_WRAPPER = 0x0010
+
+ /**
+ * Mark packet as read-only. Default is a writeable packet.
+ */
+ const val READONLY_PACKET = 0x0020
+
+ /**
+ * Use a compact form of RDF.
+ * The compact form is the default serialization format (this flag is technically ignored).
+ * To serialize to the canonical form, set the flag USE_CANONICAL_FORMAT.
+ * If both flags "compact" and "canonical" are set, canonical is used.
+ */
+ const val USE_COMPACT_FORMAT = 0x0040
+
+ /**
+ * Use the canonical form of RDF if set. By default the compact form is used
+ */
+ const val USE_CANONICAL_FORMAT = 0x0080
+
+ /**
+ * Omit the <x:xmpmeta>-tag
+ */
+ const val OMIT_XMPMETA_ELEMENT = 0x1000
+
+ /**
+ * Sort the struct properties and qualifier before serializing
+ */
+ const val SORT = 0x2000
+ }
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPAliasInfo.kt b/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPAliasInfo.kt
new file mode 100644
index 0000000..e87d028
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPAliasInfo.kt
@@ -0,0 +1,42 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.properties
+
+import com.ashampoo.xmp.options.AliasOptions
+
+/**
+ * This interface is used to return info about an alias.
+ */
+interface XMPAliasInfo {
+
+ /**
+ * @return Returns the namespace URI for the base property.
+ */
+ fun getNamespace(): String
+
+ /**
+ * @return Returns the default prefix for the given base property.
+ */
+ fun getPrefix(): String
+
+ /**
+ * @return Returns the path of the base property.
+ */
+ fun getPropName(): String
+
+ /**
+ * @return Returns the kind of the alias. This can be a direct alias
+ * (ARRAY), a simple property to an ordered array
+ * (ARRAY_ORDERED), to an alternate array
+ * (ARRAY_ALTERNATE) or to an alternate text array
+ * (ARRAY_ALT_TEXT).
+ */
+ fun getAliasForm(): AliasOptions
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPProperty.kt b/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPProperty.kt
new file mode 100644
index 0000000..ebc50a8
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPProperty.kt
@@ -0,0 +1,35 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.properties
+
+import com.ashampoo.xmp.options.PropertyOptions
+
+/**
+ * This interface is used to return a text property together with its and options.
+ */
+interface XMPProperty {
+
+ /**
+ * @return Returns the value of the property.
+ */
+ fun getValue(): String?
+
+ /**
+ * @return Returns the options of the property.
+ */
+ fun getOptions(): PropertyOptions
+
+ /**
+ * Only set by {@link XMPMeta.getLocalizedText}.
+ *
+ * @return Returns the language of the alt-text item.
+ */
+ fun getLanguage(): String?
+
+}
diff --git a/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPPropertyInfo.kt b/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPPropertyInfo.kt
new file mode 100644
index 0000000..df1c7d0
--- /dev/null
+++ b/src/commonMain/kotlin/com/ashampoo/xmp/properties/XMPPropertyInfo.kt
@@ -0,0 +1,39 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2006 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+package com.ashampoo.xmp.properties
+
+import com.ashampoo.xmp.options.PropertyOptions
+
+/**
+ * This interface is used to return a property together with its path and namespace.
+ * It is returned when properties are iterated with the XMPIterator
.
+ */
+interface XMPPropertyInfo : XMPProperty {
+
+ /**
+ * @return Returns the namespace of the property
+ */
+ fun getNamespace(): String
+
+ /**
+ * @return Returns the path of the property, but only if returned by the iterator.
+ */
+ fun getPath(): String
+
+ /**
+ * @return Returns the value of the property.
+ */
+ override fun getValue(): String
+
+ /**
+ * @return Returns the options of the property.
+ */
+ override fun getOptions(): PropertyOptions
+
+}
diff --git a/src/commonTest/kotlin/com/ashampoo/xmp/RewriteXmpTest.kt b/src/commonTest/kotlin/com/ashampoo/xmp/RewriteXmpTest.kt
new file mode 100644
index 0000000..c204ca7
--- /dev/null
+++ b/src/commonTest/kotlin/com/ashampoo/xmp/RewriteXmpTest.kt
@@ -0,0 +1,202 @@
+package com.ashampoo.xmp
+
+import com.ashampoo.xmp.options.SerializeOptions
+import com.goncalossilva.resources.Resource
+import kotlinx.io.files.Path
+import kotlinx.io.files.sink
+import kotlin.test.Test
+import kotlin.test.fail
+
+/**
+ * Different tests where XMP is loaded and written.
+ * One test loads from a source of various real-world XMP files.
+ * The other tests ensure that there is no data loss on roundtrips and conversions.
+ */
+class RewriteXmpTest {
+
+ private val xmpSerializeOptionsCompact =
+ SerializeOptions()
+ .setOmitXmpMetaElement(false)
+ .setOmitPacketWrapper(false)
+ .setUseCompactFormat(true)
+ .setUseCanonicalFormat(false)
+ .setSort(true)
+
+ private val xmpSerializeOptionsCanonical =
+ SerializeOptions()
+ .setOmitXmpMetaElement(false)
+ .setOmitPacketWrapper(false)
+ .setUseCompactFormat(false)
+ .setUseCanonicalFormat(true)
+ .setSort(true)
+
+ /**
+ * Regression test based on a fixed small set of test files.
+ */
+ @OptIn(ExperimentalStdlibApi::class)
+ @Test
+ fun testRewriteXmp() {
+
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 1..TEST_PHOTO_COUNT) {
+
+ val originalXmp = getOriginalXmp(index)
+
+ val xmpMeta = XMPMetaFactory.parseFromString(originalXmp)
+
+ val actualCompactXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptionsCompact)
+ val actualCanonicalXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptionsCanonical)
+
+ val expectedCompactXmp = getFormattedCompactXmp(index)
+ val expectedCanonicalXmp = getFormattedCanonicalXmp(index)
+
+ val equals = expectedCompactXmp.contentEquals(actualCompactXmp) &&
+ expectedCanonicalXmp.contentEquals(actualCanonicalXmp)
+
+ if (!equals) {
+
+ Path("build/sample_${index}_formatted_compact.xmp").sink().use {
+ it.write(actualCompactXmp.encodeToByteArray())
+ }
+
+ Path("build/sample_${index}_formatted_canonical.xmp").sink().use {
+ it.write(actualCanonicalXmp.encodeToByteArray())
+ }
+
+ fail("XMP for sample $index looks different after rewrite.")
+ }
+ }
+ }
+
+ /**
+ * Loading a compact formatted file and saving it should result in the same file.
+ */
+ @OptIn(ExperimentalStdlibApi::class)
+ @Test
+ fun testRoundtripCompact() {
+
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 1..TEST_PHOTO_COUNT) {
+
+ val originalXmp = getFormattedCompactXmp(index)
+
+ val xmpMeta = XMPMetaFactory.parseFromString(originalXmp)
+
+ val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptionsCompact)
+
+ val equals = originalXmp.contentEquals(newXmp)
+
+ if (!equals) {
+
+ Path("build/sample_${index}_roundtrip_compact.xmp").sink().use {
+ it.write(newXmp.encodeToByteArray())
+ }
+
+ fail("XMP for sample $index looks different after compact roundtrip.")
+ }
+ }
+ }
+
+ /**
+ * Loading a compact formatted file and saving it should result in the same file.
+ */
+ @OptIn(ExperimentalStdlibApi::class)
+ @Test
+ fun testRoundtripCanonical() {
+
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 1..TEST_PHOTO_COUNT) {
+
+ val originalXmp = getFormattedCanonicalXmp(index)
+
+ val xmpMeta = XMPMetaFactory.parseFromString(originalXmp)
+
+ val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptionsCanonical)
+
+ val equals = originalXmp.contentEquals(newXmp)
+
+ if (!equals) {
+
+ Path("build/sample_${index}_roundtrip_canonical.xmp").sink().use {
+ it.write(newXmp.encodeToByteArray())
+ }
+
+ fail("XMP for sample $index looks different after canonical roundtrip.")
+ }
+ }
+ }
+
+ /**
+ * Conversion from compact to canonical should be lossless.
+ */
+ @OptIn(ExperimentalStdlibApi::class)
+ @Test
+ fun testConvertCompactToCanonical() {
+
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 1..TEST_PHOTO_COUNT) {
+
+ val compactXmp = getFormattedCompactXmp(index)
+
+ val xmpMeta = XMPMetaFactory.parseFromString(compactXmp)
+
+ val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptionsCanonical)
+
+ val equals = newXmp.contentEquals(getFormattedCanonicalXmp(index))
+
+ if (!equals) {
+
+ Path("build/sample_${index}_compact_to_canonical.xmp").sink().use {
+ it.write(newXmp.encodeToByteArray())
+ }
+
+ fail("XMP for sample $index looks different after compact to canonical conversion.")
+ }
+ }
+ }
+
+ /**
+ * Conversion from compact to canonical should be lossless.
+ */
+ @OptIn(ExperimentalStdlibApi::class)
+ @Test
+ fun testConvertCanonicalToCompact() {
+
+ @Suppress("LoopWithTooManyJumpStatements")
+ for (index in 1..TEST_PHOTO_COUNT) {
+
+ val canonicalXmp = getFormattedCanonicalXmp(index)
+
+ val xmpMeta = XMPMetaFactory.parseFromString(canonicalXmp)
+
+ val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptionsCompact)
+
+ val equals = newXmp.contentEquals(getFormattedCompactXmp(index))
+
+ if (!equals) {
+
+ Path("build/sample_${index}_canonical_to_compact.xmp").sink().use {
+ it.write(newXmp.encodeToByteArray())
+ }
+
+ fail("XMP for sample $index looks different after canonical to compact conversion.")
+ }
+ }
+ }
+
+ private fun getOriginalXmp(index: Int): String =
+ Resource("$RESOURCE_PATH/sample_$index.xmp").readText()
+
+ private fun getFormattedCompactXmp(index: Int): String =
+ Resource("$RESOURCE_PATH/sample_${index}_formatted_compact.xmp").readText()
+
+ private fun getFormattedCanonicalXmp(index: Int): String =
+ Resource("$RESOURCE_PATH/sample_${index}_formatted_canonical.xmp").readText()
+
+ companion object {
+
+ const val TEST_PHOTO_COUNT = 100
+
+ private const val RESOURCE_PATH: String = "src/commonTest/resources/com/ashampoo/xmp"
+ }
+}
diff --git a/src/commonTest/kotlin/com/ashampoo/xmp/impl/UtilsTest.kt b/src/commonTest/kotlin/com/ashampoo/xmp/impl/UtilsTest.kt
new file mode 100644
index 0000000..8dc3fe7
--- /dev/null
+++ b/src/commonTest/kotlin/com/ashampoo/xmp/impl/UtilsTest.kt
@@ -0,0 +1,58 @@
+package com.ashampoo.xmp.impl
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class UtilsTest {
+
+ @Test
+ fun testReplaceControlCharsWithSpace() {
+
+ /* Normal chars - nothing should be changed. */
+ assertEquals(
+ "Ashampoo GmbH & Co. KG",
+ Utils.replaceControlCharsWithSpace("Ashampoo GmbH & Co. KG")
+ )
+
+ /* Random Control characters in between */
+ assertEquals(
+ "Ashampoo GmbH & Co. KG",
+ Utils.replaceControlCharsWithSpace("Ashampoo\u0000GmbH\u001B&\nCo.\rKG")
+ )
+
+ /* Individual control characters */
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0000")) // NUL
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0001")) // SOH
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0002")) // STX
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0003")) // ETX
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0004")) // EOT
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0005")) // ENQ
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0006")) // ACK
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0007")) // BEL
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0008")) // BS
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0009")) // HT
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u000A")) // LF
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u000B")) // VT
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u000C")) // FF
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u000D")) // CR
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u000E")) // SO
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u000F")) // SI
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0010")) // DLE
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0011")) // DC1
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0012")) // DC2
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0013")) // DC3
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0014")) // DC4
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0015")) // NAK
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0016")) // SYN
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0017")) // ETB
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0018")) // CAN
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u0019")) // EM
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u001A")) // SUB
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u001B")) // ESC
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u001C")) // FS
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u001D")) // GS
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u001E")) // RS
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u001F")) // US
+ assertEquals(" ", Utils.replaceControlCharsWithSpace("\u007F")) // DEL
+ }
+}
diff --git a/src/commonTest/resources/com/ashampoo/xmp/.gitattributes b/src/commonTest/resources/com/ashampoo/xmp/.gitattributes
new file mode 100644
index 0000000..4f2b797
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/.gitattributes
@@ -0,0 +1,2 @@
+# Git should not touch original CRLF in XMP
+*.xmp binary
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_1.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_1.xmp
new file mode 100644
index 0000000..95fc6f6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_1.xmp
@@ -0,0 +1,208 @@
+
+
+
+
+
+ 1
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ sharpen
+ 000000400000003f0000003f
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+
+ 0
+ flip
+ ffffffff
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ basecurve
+ gz09eJxjYIAAM6vnNnqyn22E9n235b6aa3cy6rVdRaK9/Y970fYf95bbMzA0QPEoGEqADYnNhCELiVMAudcSGA==
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ clipping
+ 00000000e040f23c4ee4053efce6413f80c75b3f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ clipping
+ 00000000501a1c3d0c5cb53d9a16443feef9503f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ colisa
+ d0ccccbdd0cccc3dd0cc4cbd
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f0000803ecdcc4c3e
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ sharpen
+ 000000400000803f0000003f
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f9a99993ecdcc4c3e
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+
+ 0
+ levels
+ 0000000000000000000048420000c842d9899d3dc332d53e8bac483f
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ demosaic
+ 0000000000000000000000000100000000000000
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ cacorrect
+ 32000000
+
+
+
+ 12
+ 0
+ 2
+
+
+
+
+
+ dxfoto.ru
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+
+
+
+ 55,54.678955N
+ 36,57.808914E
+ 2.2.0.0
+
+
+
+
+
+ dxfoto|published
+
+
+
+
+
+ 0
+
+
+
+ _MG_9604.CR2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_10.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_10.xmp
new file mode 100644
index 0000000..858fe61
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_10.xmp
@@ -0,0 +1,276 @@
+
+
+
+
+
+ 633/100
+ True
+ 1.0.2
+ 0/1
+ 0
+ True
+ EF300mm f/2.8L IS II USM
+ 491
+ 300/1 300/1 0/0 0/0
+ 4250000105
+ 043052000677
+ True
+
+
+
+ image/jpeg
+
+
+
+ EF300mm f/2.8L IS II USM
+
+
+
+ 2019-05-10T16:43:59.77
+
+
+
+ 2019-05-10T16:43:59.77
+ Adobe Photoshop Lightroom Classic 8.2.1 (Windows)
+ 2019-05-13T14:38:08+02:00
+ 2019-05-13T14:38:08+02:00
+ 2
+
+
+
+
+ ADEF0118BDEC2C325E09C9436FBF380D
+ ADEF0118BDEC2C325E09C9436FBF380D
+
+ xmp.did:bac39157-4103-8c49-8e8f-eab3a03e7d76
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:bac39157-4103-8c49-8e8f-eab3a03e7d76
+ Adobe Photoshop Lightroom Classic 8.2.1 (Windows)
+ 2019-05-13T14:38:08+02:00
+
+
+
+ xmp.iid:bac39157-4103-8c49-8e8f-eab3a03e7d76
+ ADEF0118BDEC2C325E09C9436FBF380D
+
+
+
+ True
+ 1
+ -14
+ 0
+ 0
+ Adobe Standard
+ 661433344C8532AFA5A1E9091401E43C
+ +7
+ 25
+ 50
+ 50
+ 0
+ False
+ -4.48239
+ 0.800276
+ 0
+ 0.304876
+ 0.826385
+ 0.304076
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.56
+ 0
+ 0
+ 0
+ True
+ True
+ -100
+ +8
+ +14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ 94C1E93C14C53105E14C921B38A367FE
+ 100
+ 1
+ Canon EOS-1Ds Mark III (Canon EF 300mm f2.8L IS II USM) - RAW.lcp
+ Adobe (Canon EF 300mm f/2.8L IS II USM)
+ LensDefaults
+ 100
+
+
+
+ 0
+ 0
+ -14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 14
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _V7A9726.CR2
+ 0
+ 0
+ +25
+ +4
+ +3
+ +14
+ 0
+ 0
+ 0
+ 0
+ -30
+ 0
+ +85
+ 25
+ 51
+ +1.0
+ 46
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5500
+ +10
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 78, 66
+ 199, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2.1
+ +17
+ 0
+ Daylight
+ +35
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_100.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_100.xmp
new file mode 100644
index 0000000..4225bd9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_100.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+ test
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_100_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_100_formatted_canonical.xmp
new file mode 100644
index 0000000..2dcb6be
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_100_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.741Z
+ false
+ false
+ 1
+
+
+ test
+
+
+ 2023-07-03T10:24:16.000
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.741Z
+ 2023-07-03T10:24:16.000
+ 1
+ F4B0CBDE-3B21-46AB-BAD0-9BEA764E84D9
+ xmp.iid:6E5C4F82-0EAB-4EE1-9AB3-0748412A6A85
+ F4B0CBDE-3B21-46AB-BAD0-9BEA764E84D9
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_100_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_100_formatted_compact.xmp
new file mode 100644
index 0000000..4124013
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_100_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ test
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_10_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_10_formatted_canonical.xmp
new file mode 100644
index 0000000..f3494b2
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_10_formatted_canonical.xmp
@@ -0,0 +1,241 @@
+
+
+
+
+ 633/100
+ True
+ 1.0.2
+ 0/1
+ 0
+ True
+ EF300mm f/2.8L IS II USM
+ 491
+ 300/1 300/1 0/0 0/0
+ 4250000105
+ 043052000677
+ True
+ True
+ 1
+ -14
+ 0
+ 0
+ Adobe Standard
+ 661433344C8532AFA5A1E9091401E43C
+ +7
+ 25
+ 50
+ 50
+ 0
+ False
+ -4.48239
+ 0.800276
+ 0
+ 0.304876
+ 0.826385
+ 0.304076
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.56
+ 0
+ 0
+ 0
+ True
+ True
+ -100
+ +8
+ +14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ 94C1E93C14C53105E14C921B38A367FE
+ 100
+ 1
+ Canon EOS-1Ds Mark III (Canon EF 300mm f2.8L IS II USM) - RAW.lcp
+ Adobe (Canon EF 300mm f/2.8L IS II USM)
+ LensDefaults
+ 100
+
+
+
+
+
+ 0
+ 0
+ -14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 14
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _V7A9726.CR2
+ 0
+ 0
+ +25
+ +4
+ +3
+ +14
+ 0
+ 0
+ 0
+ 0
+ -30
+ 0
+ +85
+ 25
+ 51
+ +1.0
+ 46
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5500
+ +10
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 78, 66
+ 199, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2.1
+ +17
+ 0
+ Daylight
+ +35
+ image/jpeg
+ EF300mm f/2.8L IS II USM
+ 2019-05-10T16:43:59.77
+ 2019-05-10T16:43:59.77
+ Adobe Photoshop Lightroom Classic 8.2.1 (Windows)
+ 2019-05-13T14:38:08+02:00
+ 2019-05-13T14:38:08+02:00
+ 2
+
+
+ ADEF0118BDEC2C325E09C9436FBF380D
+ ADEF0118BDEC2C325E09C9436FBF380D
+
+
+ xmp.did:bac39157-4103-8c49-8e8f-eab3a03e7d76
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:bac39157-4103-8c49-8e8f-eab3a03e7d76
+ Adobe Photoshop Lightroom Classic 8.2.1 (Windows)
+ 2019-05-13T14:38:08+02:00
+
+
+
+
+ xmp.iid:bac39157-4103-8c49-8e8f-eab3a03e7d76
+ ADEF0118BDEC2C325E09C9436FBF380D
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_10_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_10_formatted_compact.xmp
new file mode 100644
index 0000000..55a33c6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_10_formatted_compact.xmp
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 78, 66
+ 199, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_11.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_11.xmp
new file mode 100644
index 0000000..29a11ec
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_11.xmp
@@ -0,0 +1,103 @@
+
+
+
+
+
+ 50/100
+ 19420
+ 150.0 mm f/2.8
+ 153
+ 1500/10 1500/10 28/10 28/10
+ 6301277
+
+
+
+ image/jpeg
+
+
+
+ 2013-05-01T14:55:50.00
+
+
+
+ 2013-05-01T14:55:50.00
+ Adobe Photoshop Lightroom 4.1 (Windows)
+ 2013-05-07T21:28:50+02:00
+ 2013-05-07T21:28:50+02:00
+ 1
+
+
+
+
+ xmp.did:728440284CB7E2119FF5F9889E349A1A
+ xmp.iid:728440284CB7E2119FF5F9889E349A1A
+ AFB5C6C2AED069D9EBB2E354E01B819B
+
+ xmp.did:738440284CB7E2119FF5F9889E349A1A
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:728440284CB7E2119FF5F9889E349A1A
+ Adobe Photoshop Lightroom 4.1 (Windows)
+ 2013-05-07T21:28:50+02:00
+
+
+ converted
+ from image/jpeg to image/x-nikon-nef
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:738440284CB7E2119FF5F9889E349A1A
+ Adobe Photoshop Lightroom 4.1 (Windows)
+ 2013-05-07T21:28:50+02:00
+
+
+
+ xmp.iid:738440284CB7E2119FF5F9889E349A1A
+ AFB5C6C2AED069D9EBB2E354E01B819B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_11_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_11_formatted_canonical.xmp
new file mode 100644
index 0000000..54a190a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_11_formatted_canonical.xmp
@@ -0,0 +1,78 @@
+
+
+
+
+ 50/100
+ 19420
+ 150.0 mm f/2.8
+ 153
+ 1500/10 1500/10 28/10 28/10
+ 6301277
+ image/jpeg
+ 2013-05-01T14:55:50.00
+ 2013-05-01T14:55:50.00
+ Adobe Photoshop Lightroom 4.1 (Windows)
+ 2013-05-07T21:28:50+02:00
+ 2013-05-07T21:28:50+02:00
+ 1
+
+
+ xmp.did:728440284CB7E2119FF5F9889E349A1A
+ xmp.iid:728440284CB7E2119FF5F9889E349A1A
+ AFB5C6C2AED069D9EBB2E354E01B819B
+
+
+ xmp.did:738440284CB7E2119FF5F9889E349A1A
+
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:728440284CB7E2119FF5F9889E349A1A
+ Adobe Photoshop Lightroom 4.1 (Windows)
+ 2013-05-07T21:28:50+02:00
+
+
+
+
+ converted
+ from image/jpeg to image/x-nikon-nef
+
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:738440284CB7E2119FF5F9889E349A1A
+ Adobe Photoshop Lightroom 4.1 (Windows)
+ 2013-05-07T21:28:50+02:00
+
+
+
+
+ xmp.iid:738440284CB7E2119FF5F9889E349A1A
+ AFB5C6C2AED069D9EBB2E354E01B819B
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_11_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_11_formatted_compact.xmp
new file mode 100644
index 0000000..1994a75
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_11_formatted_compact.xmp
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_12.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_12.xmp
new file mode 100644
index 0000000..4d7f85b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_12.xmp
@@ -0,0 +1,308 @@
+
+
+
+
+
+ True
+ 100-300mm F4.5-5.6
+ 25
+ 100/1 300/1 45/10 56/10
+
+
+
+
+
+ 4
+
+
+ image/jpeg
+
+
+
+ 2011-01-29T15:42:53+01:00
+
+
+
+ 2011-01-29T15:42:53+01:00
+ Adobe Photoshop Lightroom Classic 8.2 (Windows)
+ 2019-03-28T18:02:40+01:00
+ 2019-03-28T18:02:40+01:00
+ 4
+
+
+
+
+ AF0E72CD7E970A92B6DA952C7C4831B5
+ AF0E72CD7E970A92B6DA952C7C4831B5
+
+ xmp.did:df60a963-bb4b-6f45-9f40-7123f06fd205
+
+
+
+ derived
+ converted from image/x-sony-arw to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:df60a963-bb4b-6f45-9f40-7123f06fd205
+ Adobe Photoshop Lightroom Classic 8.2 (Windows)
+ 2019-03-28T18:02:40+01:00
+
+
+
+ xmp.iid:df60a963-bb4b-6f45-9f40-7123f06fd205
+ AF0E72CD7E970A92B6DA952C7C4831B5
+
+
+
+ True
+ 1
+ -40
+ 0
+ 0
+ Adobe Standard
+ 662D3C49A613E074295EE188A7E98BB6
+ +33
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 1
+ 0
+ 0.047428
+ 0.86254
+ 0.136309
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ -0.15
+ 0
+ 0
+ 0
+ True
+ True
+ -100
+ +8
+ +14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ LensDefaults
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.2
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 14
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _DSC3591.ARW
+ 0
+ 0
+ +5
+ +6
+ +17
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +76
+ 25
+ 51
+ +1.0
+ 44
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5500
+ +10
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 65, 57
+ 187, 192
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2
+ +17
+ 0
+ Daylight
+ +35
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_12_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_12_formatted_canonical.xmp
new file mode 100644
index 0000000..936b38e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_12_formatted_canonical.xmp
@@ -0,0 +1,278 @@
+
+
+
+
+ True
+ 100-300mm F4.5-5.6
+ 25
+ 100/1 300/1 45/10 56/10
+ True
+ 1
+ -40
+ 0
+ 0
+ Adobe Standard
+ 662D3C49A613E074295EE188A7E98BB6
+ +33
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 1
+ 0
+ 0.047428
+ 0.86254
+ 0.136309
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ -0.15
+ 0
+ 0
+ 0
+ True
+ True
+ -100
+ +8
+ +14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ LensDefaults
+
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.2
+
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 14
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _DSC3591.ARW
+ 0
+ 0
+ +5
+ +6
+ +17
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +76
+ 25
+ 51
+ +1.0
+ 44
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5500
+ +10
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 65, 57
+ 187, 192
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2
+ +17
+ 0
+ Daylight
+ +35
+
+
+ 4
+
+
+ image/jpeg
+ 2011-01-29T15:42:53+01:00
+ 2011-01-29T15:42:53+01:00
+ Adobe Photoshop Lightroom Classic 8.2 (Windows)
+ 2019-03-28T18:02:40+01:00
+ 2019-03-28T18:02:40+01:00
+ 4
+
+
+ AF0E72CD7E970A92B6DA952C7C4831B5
+ AF0E72CD7E970A92B6DA952C7C4831B5
+
+
+ xmp.did:df60a963-bb4b-6f45-9f40-7123f06fd205
+
+
+
+
+ derived
+ converted from image/x-sony-arw to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:df60a963-bb4b-6f45-9f40-7123f06fd205
+ Adobe Photoshop Lightroom Classic 8.2 (Windows)
+ 2019-03-28T18:02:40+01:00
+
+
+
+
+ xmp.iid:df60a963-bb4b-6f45-9f40-7123f06fd205
+ AF0E72CD7E970A92B6DA952C7C4831B5
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_12_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_12_formatted_compact.xmp
new file mode 100644
index 0000000..ce8e9a5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_12_formatted_compact.xmp
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
+
+ Profiles
+
+
+
+
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 65, 57
+ 187, 192
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_13.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_13.xmp
new file mode 100644
index 0000000..3b66912
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_13.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_13_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_13_formatted_canonical.xmp
new file mode 100644
index 0000000..e0b6c54
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_13_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 1
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_13_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_13_formatted_compact.xmp
new file mode 100644
index 0000000..be6643b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_13_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_14.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_14.xmp
new file mode 100644
index 0000000..2b09f15
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_14.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_14_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_14_formatted_canonical.xmp
new file mode 100644
index 0000000..63bda32
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_14_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_14_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_14_formatted_compact.xmp
new file mode 100644
index 0000000..7fec24e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_14_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_15.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_15.xmp
new file mode 100644
index 0000000..7ba7b38
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_15.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+ 2019-10-18T11:56:16.57
+ Windows Photo Editor 10.0.10011.16384
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_15_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_15_formatted_canonical.xmp
new file mode 100644
index 0000000..42419b3
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_15_formatted_canonical.xmp
@@ -0,0 +1,12 @@
+
+
+
+
+ 2019-10-18T11:56:16.57
+ Windows Photo Editor 10.0.10011.16384
+ 3
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_15_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_15_formatted_compact.xmp
new file mode 100644
index 0000000..2d52919
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_15_formatted_compact.xmp
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_16.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_16.xmp
new file mode 100644
index 0000000..786261e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_16.xmp
@@ -0,0 +1,326 @@
+
+
+
+
+
+ 231/100
+ 1.2.1
+ 0/1
+ 0
+ EF100mm f/2.8L Macro IS USM
+ 254
+ 100/1 100/1 0/0 0/0
+ 0000079489
+ 152028001073
+
+
+
+ image/jpeg
+
+
+
+ EF100mm f/2.8L Macro IS USM
+
+
+
+ 2018-12-10T11:50:52.82
+
+
+
+ 2018-12-10T11:50:52.82
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-15T19:42:58-05:00
+ 2018-12-15T19:42:58-05:00
+ 2
+
+
+
+
+ 59A0143D38A8DE402D6714C9B34AA0D7
+ 59A0143D38A8DE402D6714C9B34AA0D7
+
+ xmp.did:9adf2915-0e6f-44b1-8d9a-98f1ee410c6b
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:9adf2915-0e6f-44b1-8d9a-98f1ee410c6b
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-15T19:42:58-05:00
+
+
+
+ xmp.iid:9adf2915-0e6f-44b1-8d9a-98f1ee410c6b
+ 59A0143D38A8DE402D6714C9B34AA0D7
+
+
+
+ True
+ 0
+ 0
+ 0
+ 0
+ Adobe Standard
+ 87FB0EDC503E332309FB5DE5C5C65125
+ +60
+ 25
+ 50
+ 50
+ +20
+ False
+ -14.7646
+ 0.782795
+ 0
+ 0.372586
+ 0.835012
+ 0.268112
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.10
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.402607
+ 0.186808
+ 1.000000
+ Mask/Gradient
+ 0.475708
+ 0.405867
+
+
+
+
+ 0.500000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0
+ +2
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.233467
+ 0.241733
+ 0.000000
+ 0.000000
+ 0.576971
+ 0.000000
+ 0.000000
+ 0.684684
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.777253
+ Correction
+
+
+
+ 23
+ 50
+ 739773875
+ 25
+ 0
+ 0
+ True
+ True
+ -15
+ +20
+ -5
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+ +74
+ +38
+ +36
+ 0
+ -22
+ 0
+ +20
+ 0
+ 0
+ 50
+ 25
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ -25
+ 50
+ 0
+ 53
+ 0
+ 1
+ 11.0
+ _Z3A0583.CR2
+ 0
+ 0
+ +10
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +44
+ 0
+ +20
+ 25
+ 0
+ +1.0
+ 75
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4250
+ +6
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 18
+ 63, 55
+ 130, 133
+ 187, 190
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ +64
+ 0
+ As Shot
+ +10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_16_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_16_formatted_canonical.xmp
new file mode 100644
index 0000000..a4b3a37
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_16_formatted_canonical.xmp
@@ -0,0 +1,297 @@
+
+
+
+
+ 231/100
+ 1.2.1
+ 0/1
+ 0
+ EF100mm f/2.8L Macro IS USM
+ 254
+ 100/1 100/1 0/0 0/0
+ 0000079489
+ 152028001073
+ True
+ 0
+ 0
+ 0
+ 0
+ Adobe Standard
+ 87FB0EDC503E332309FB5DE5C5C65125
+ +60
+ 25
+ 50
+ 50
+ +20
+ False
+ -14.7646
+ 0.782795
+ 0
+ 0.372586
+ 0.835012
+ 0.268112
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.10
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.402607
+ 0.186808
+ 1.000000
+ Mask/Gradient
+ 0.475708
+ 0.405867
+
+
+
+
+
+
+ 0.500000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0
+ +2
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.233467
+ 0.241733
+ 0.000000
+ 0.000000
+ 0.576971
+ 0.000000
+ 0.000000
+ 0.684684
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.777253
+ Correction
+
+
+
+
+ 23
+ 50
+ 739773875
+ 25
+ 0
+ 0
+ True
+ True
+ -15
+ +20
+ -5
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+
+
+ +74
+ +38
+ +36
+ 0
+ -22
+ 0
+ +20
+ 0
+ 0
+ 50
+ 25
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ -25
+ 50
+ 0
+ 53
+ 0
+ 1
+ 11.0
+ _Z3A0583.CR2
+ 0
+ 0
+ +10
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +44
+ 0
+ +20
+ 25
+ 0
+ +1.0
+ 75
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4250
+ +6
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 18
+ 63, 55
+ 130, 133
+ 187, 190
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ +64
+ 0
+ As Shot
+ +10
+ image/jpeg
+ EF100mm f/2.8L Macro IS USM
+ 2018-12-10T11:50:52.82
+ 2018-12-10T11:50:52.82
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-15T19:42:58-05:00
+ 2018-12-15T19:42:58-05:00
+ 2
+
+
+ 59A0143D38A8DE402D6714C9B34AA0D7
+ 59A0143D38A8DE402D6714C9B34AA0D7
+
+
+ xmp.did:9adf2915-0e6f-44b1-8d9a-98f1ee410c6b
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:9adf2915-0e6f-44b1-8d9a-98f1ee410c6b
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-15T19:42:58-05:00
+
+
+
+
+ xmp.iid:9adf2915-0e6f-44b1-8d9a-98f1ee410c6b
+ 59A0143D38A8DE402D6714C9B34AA0D7
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_16_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_16_formatted_compact.xmp
new file mode 100644
index 0000000..b779eca
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_16_formatted_compact.xmp
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 18
+ 63, 55
+ 130, 133
+ 187, 190
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_17.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_17.xmp
new file mode 100644
index 0000000..e0afe93
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_17.xmp
@@ -0,0 +1,41 @@
+
+
+
+
+
+ 85.0 mm f/1.4
+
+
+
+ Lightroom
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_17_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_17_formatted_canonical.xmp
new file mode 100644
index 0000000..8c30bb5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_17_formatted_canonical.xmp
@@ -0,0 +1,13 @@
+
+
+
+
+ 85.0 mm f/1.4
+ Lightroom
+ 5
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_17_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_17_formatted_compact.xmp
new file mode 100644
index 0000000..85a2b2e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_17_formatted_compact.xmp
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_18.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_18.xmp
new file mode 100644
index 0000000..28360bd
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_18.xmp
@@ -0,0 +1,302 @@
+
+
+
+
+
+ 41/100
+ True
+ 1.1.0
+ 0/1
+ 0
+ EF100mm f/2.8L Macro IS USM
+ 254
+ 100/1 100/1 0/0 0/0
+ 630602135
+ True
+
+
+
+
+
+ Thomas Lipke
+
+
+ image/jpeg
+
+
+ 500px
+ Bauminvest
+ Christiane 50Jahre
+
+
+
+
+
+ 2011-08-22T20:38:57.052
+
+
+
+ 2011-08-22T20:38:57
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-06T14:24:24+01:00
+ 2018-12-06T14:24:24+01:00
+ 4
+
+
+
+
+ adobe:docid:photoshop:4d7d0030-12d9-e24c-8a7b-d4db80e48571
+ xmp.iid:b35f35ff-80a5-4770-8f73-62231578b6a2
+ xmp.did:636d9652-7318-4649-a50e-af04c5b63c49
+
+ xmp.did:88140087-1c67-45cf-86de-4ad5d4850f3f
+
+
+
+ saved
+ /metadata
+ xmp.iid:636d9652-7318-4649-a50e-af04c5b63c49
+ Adobe Photoshop Lightroom 4.3 (Macintosh)
+ 2012-12-31T00:32:53+01:00
+
+
+ saved
+ /metadata
+ xmp.iid:0cce0e8e-d90e-4149-ab21-c26e719d9d83
+ Adobe Photoshop Lightroom 6.8 (Macintosh)
+ 2017-01-20T18:38:30+01:00
+
+
+ derived
+ converted from image/x-canon-cr2 to image/tiff
+
+
+ saved
+ /
+ xmp.iid:cc71ce9a-e40c-42c4-b2e8-b87080820372
+ Adobe Photoshop Camera Raw 11.0 (Macintosh)
+ 2018-12-06T14:20:52+01:00
+
+
+ saved
+ /
+ xmp.iid:b35f35ff-80a5-4770-8f73-62231578b6a2
+ Adobe Photoshop CC 2019 (Macintosh)
+ 2018-12-06T14:21:55+01:00
+
+
+ derived
+ converted from image/tiff to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:88140087-1c67-45cf-86de-4ad5d4850f3f
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-06T14:24:24+01:00
+
+
+
+ xmp.iid:88140087-1c67-45cf-86de-4ad5d4850f3f
+ xmp.did:636d9652-7318-4649-a50e-af04c5b63c49
+
+
+
+ True
+ 0
+ 0
+ 0
+ 0
+ Embedded
+ 54650A341B5B5CCAE8442D0B43A92BCE
+ 0
+ 0
+ 0
+ False
+ 0
+ 0.915225
+ 0
+ 0.073085
+ 0.92438
+ 0.071379
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ 0
+ 0
+ True
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ 0
+ 0
+ As Shot
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_18_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_18_formatted_canonical.xmp
new file mode 100644
index 0000000..8272474
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_18_formatted_canonical.xmp
@@ -0,0 +1,280 @@
+
+
+
+
+ 41/100
+ True
+ 1.1.0
+ 0/1
+ 0
+ EF100mm f/2.8L Macro IS USM
+ 254
+ 100/1 100/1 0/0 0/0
+ 630602135
+ True
+ True
+ 0
+ 0
+ 0
+ 0
+ Embedded
+ 54650A341B5B5CCAE8442D0B43A92BCE
+ 0
+ 0
+ 0
+ False
+ 0
+ 0.915225
+ 0
+ 0.073085
+ 0.92438
+ 0.071379
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ 0
+ 0
+ True
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ 0
+ 0
+ As Shot
+ 0
+
+
+ Thomas Lipke
+
+
+ image/jpeg
+
+
+ 500px
+ Bauminvest
+ Christiane 50Jahre
+
+
+ 2011-08-22T20:38:57.052
+ 2011-08-22T20:38:57
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-06T14:24:24+01:00
+ 2018-12-06T14:24:24+01:00
+ 4
+
+
+ adobe:docid:photoshop:4d7d0030-12d9-e24c-8a7b-d4db80e48571
+ xmp.iid:b35f35ff-80a5-4770-8f73-62231578b6a2
+ xmp.did:636d9652-7318-4649-a50e-af04c5b63c49
+
+
+ xmp.did:88140087-1c67-45cf-86de-4ad5d4850f3f
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:636d9652-7318-4649-a50e-af04c5b63c49
+ Adobe Photoshop Lightroom 4.3 (Macintosh)
+ 2012-12-31T00:32:53+01:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:0cce0e8e-d90e-4149-ab21-c26e719d9d83
+ Adobe Photoshop Lightroom 6.8 (Macintosh)
+ 2017-01-20T18:38:30+01:00
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:cc71ce9a-e40c-42c4-b2e8-b87080820372
+ Adobe Photoshop Camera Raw 11.0 (Macintosh)
+ 2018-12-06T14:20:52+01:00
+
+
+
+
+ saved
+ /
+ xmp.iid:b35f35ff-80a5-4770-8f73-62231578b6a2
+ Adobe Photoshop CC 2019 (Macintosh)
+ 2018-12-06T14:21:55+01:00
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:88140087-1c67-45cf-86de-4ad5d4850f3f
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2018-12-06T14:24:24+01:00
+
+
+
+
+ xmp.iid:88140087-1c67-45cf-86de-4ad5d4850f3f
+ xmp.did:636d9652-7318-4649-a50e-af04c5b63c49
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_18_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_18_formatted_compact.xmp
new file mode 100644
index 0000000..bada3c5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_18_formatted_compact.xmp
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ Thomas Lipke
+
+
+
+
+ 500px
+ Bauminvest
+ Christiane 50Jahre
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_19.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_19.xmp
new file mode 100644
index 0000000..e2c6da1
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_19.xmp
@@ -0,0 +1,509 @@
+
+
+
+
+
+ 355/100
+ True
+ 23644
+ 24.0-70.0 mm f/2.8
+ 170
+ 240/10 700/10 28/10 28/10
+ 3036315
+ True
+
+
+
+ image/jpeg
+
+
+
+ 3
+ 2020-01-18T17:28:22.056-08:00
+
+
+ adobe:docid:photoshop:4175d938-93f1-924b-9e9c-eb3dcbc947ca
+ xmp.did:3ff520b3-d1f6-42f9-9906-354073c4a7e9
+
+
+ Adobe RGB (1998)
+ 0E471C54765BB5CE2FE1EFB43F428AEE
+
+
+
+ 2020-01-18T17:28:22
+ NIKON D850 Ver.1.02
+ 2020-02-08T08:46:48-07:00
+ 2020-02-08T08:46:48-07:00
+ 4
+
+
+
+
+ xmp.did:c8692041-63f7-4d21-bec9-e970b76b434e
+ xmp.iid:c8692041-63f7-4d21-bec9-e970b76b434e
+ FE70A58626BB309F945E62BC25DCC69F
+
+ xmp.did:c8692041-63f7-4d21-bec9-e970b76b434e
+
+
+
+ saved
+ /metadata
+ xmp.iid:edab6c79-1aab-4ae1-993c-dd79fff9441c
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:38:10-08:00
+
+
+ derived
+ converted from image/x-nikon-nef to image/tiff
+
+
+ saved
+ /
+ xmp.iid:54213404-50c4-4bc1-8b5e-ca608d491353
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:38:17-08:00
+
+
+ saved
+ /
+ xmp.iid:91ee3fcb-a536-49e8-ad83-8c8c4f8014a0
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:39:52-08:00
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:504517e5-4d2d-4ff9-ab4d-6cb7a4c1545c
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:39:52-08:00
+
+
+ saved
+ /metadata
+ xmp.iid:a153dabd-59ba-4c6e-aba1-553767255325
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:44:21-08:00
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+ saved
+ /
+ xmp.iid:07d6cc09-cb39-46e7-bc3d-0380e00b4d4b
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:44:24-08:00
+
+
+ saved
+ /
+ xmp.iid:5e666aac-e58f-41c4-82eb-4b8ba1d9c23d
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:45:53-08:00
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:817bc10c-57a5-4064-bb95-536749889727
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:45:53-08:00
+
+
+ saved
+ /
+ xmp.iid:0e0de60d-9dd4-420f-bf1e-056c4de9389b
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:52:27-08:00
+
+
+ saved
+ /metadata
+ xmp.iid:3bbbbb50-2b69-412a-8da7-ebe4b7b946ba
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T06:30:49-08:00
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+ saved
+ /
+ xmp.iid:3ff520b3-d1f6-42f9-9906-354073c4a7e9
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T06:31-08:00
+
+
+ saved
+ /
+ xmp.iid:dea5b69c-6c2a-4587-90e0-360b5bd881bd
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-25T06:32:20-08:00
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:db2154f0-29a0-4501-946a-67152d71dd28
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-25T06:32:20-08:00
+
+
+ saved
+ /metadata
+ xmp.iid:7e2dbb6a-7ecf-4717-a6ca-540cdc80f483
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T08:38:46-08:00
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+ saved
+ /
+ xmp.iid:dc4f3a33-b497-474a-86ef-7a46e93470b5
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T08:38:47-08:00
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:fd2ef984-93d1-468d-993a-f19f5630680a
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-25T08:38:59-08:00
+
+
+ saved
+ /metadata
+ xmp.iid:6858410a-315f-42d5-861d-2fa836172dac
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-27T23:29:57-08:00
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+ saved
+ /
+ xmp.iid:fac57150-ea09-4a76-a2c3-49fe413ddbc1
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-27T23:30:05-08:00
+
+
+ saved
+ /
+ xmp.iid:1be1ab9f-f595-4c62-b814-a213e55047d3
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-27T23:30:19-08:00
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:e1caa918-cfbe-476c-ae4f-088620b9c999
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-27T23:30:19-08:00
+
+
+ saved
+ /metadata
+ xmp.iid:01bd7cf8-e6a1-4b63-bce6-fb42e01d0951
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:45:03-07:00
+
+
+ saved
+ /metadata
+ xmp.iid:e50abce5-1f36-4d9f-bbba-241dc6a20e07
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:45:42-07:00
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+ saved
+ /
+ xmp.iid:5206e00c-b360-485d-8869-459d85ae3ce2
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:45:42-07:00
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:110342a5-d3aa-4257-914e-418b9629c650
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-02-08T08:46-07:00
+
+
+ saved
+ /metadata
+ xmp.iid:7f75f01b-267e-4630-a1d5-b1afb1a5bda9
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:46:18-07:00
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+ saved
+ /
+ xmp.iid:c8692041-63f7-4d21-bec9-e970b76b434e
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:46:19-07:00
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:f3d1ba30-5073-4364-b39e-b98fb1412d7b
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-02-08T08:46:48-07:00
+
+
+
+ xmp.iid:f3d1ba30-5073-4364-b39e-b98fb1412d7b
+ FE70A58626BB309F945E62BC25DCC69F
+
+
+
+ True
+ 0
+ 0
+ 0
+ 0
+ Embedded
+ 54650A341B5B5CCAE8442D0B43A92BCE
+ 0
+ 0
+ -6
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 12.1
+ 0
+ 0
+ As Shot
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_19_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_19_formatted_canonical.xmp
new file mode 100644
index 0000000..9e685a8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_19_formatted_canonical.xmp
@@ -0,0 +1,563 @@
+
+
+
+
+ 355/100
+ True
+ 23644
+ 24.0-70.0 mm f/2.8
+ 170
+ 240/10 700/10 28/10 28/10
+ 3036315
+ True
+ True
+ 0
+ 0
+ 0
+ 0
+ Embedded
+ 54650A341B5B5CCAE8442D0B43A92BCE
+ 0
+ 0
+ -6
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 12.1
+ 0
+ 0
+ As Shot
+ 0
+ image/jpeg
+ 3
+ 2020-01-18T17:28:22.056-08:00
+
+
+ adobe:docid:photoshop:4175d938-93f1-924b-9e9c-eb3dcbc947ca
+ xmp.did:3ff520b3-d1f6-42f9-9906-354073c4a7e9
+
+
+ Adobe RGB (1998)
+ 0E471C54765BB5CE2FE1EFB43F428AEE
+ 2020-01-18T17:28:22
+ NIKON D850 Ver.1.02
+ 2020-02-08T08:46:48-07:00
+ 2020-02-08T08:46:48-07:00
+ 4
+
+
+ xmp.did:c8692041-63f7-4d21-bec9-e970b76b434e
+ xmp.iid:c8692041-63f7-4d21-bec9-e970b76b434e
+ FE70A58626BB309F945E62BC25DCC69F
+
+
+ xmp.did:c8692041-63f7-4d21-bec9-e970b76b434e
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:edab6c79-1aab-4ae1-993c-dd79fff9441c
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:38:10-08:00
+
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:54213404-50c4-4bc1-8b5e-ca608d491353
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:38:17-08:00
+
+
+
+
+ saved
+ /
+ xmp.iid:91ee3fcb-a536-49e8-ad83-8c8c4f8014a0
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:39:52-08:00
+
+
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:504517e5-4d2d-4ff9-ab4d-6cb7a4c1545c
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:39:52-08:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:a153dabd-59ba-4c6e-aba1-553767255325
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:44:21-08:00
+
+
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:07d6cc09-cb39-46e7-bc3d-0380e00b4d4b
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-19T07:44:24-08:00
+
+
+
+
+ saved
+ /
+ xmp.iid:5e666aac-e58f-41c4-82eb-4b8ba1d9c23d
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:45:53-08:00
+
+
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:817bc10c-57a5-4064-bb95-536749889727
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:45:53-08:00
+
+
+
+
+ saved
+ /
+ xmp.iid:0e0de60d-9dd4-420f-bf1e-056c4de9389b
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-19T07:52:27-08:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:3bbbbb50-2b69-412a-8da7-ebe4b7b946ba
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T06:30:49-08:00
+
+
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:3ff520b3-d1f6-42f9-9906-354073c4a7e9
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T06:31-08:00
+
+
+
+
+ saved
+ /
+ xmp.iid:dea5b69c-6c2a-4587-90e0-360b5bd881bd
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-25T06:32:20-08:00
+
+
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:db2154f0-29a0-4501-946a-67152d71dd28
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-25T06:32:20-08:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:7e2dbb6a-7ecf-4717-a6ca-540cdc80f483
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T08:38:46-08:00
+
+
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:dc4f3a33-b497-474a-86ef-7a46e93470b5
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-25T08:38:47-08:00
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:fd2ef984-93d1-468d-993a-f19f5630680a
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-25T08:38:59-08:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:6858410a-315f-42d5-861d-2fa836172dac
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-27T23:29:57-08:00
+
+
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:fac57150-ea09-4a76-a2c3-49fe413ddbc1
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-01-27T23:30:05-08:00
+
+
+
+
+ saved
+ /
+ xmp.iid:1be1ab9f-f595-4c62-b814-a213e55047d3
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-27T23:30:19-08:00
+
+
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:e1caa918-cfbe-476c-ae4f-088620b9c999
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-01-27T23:30:19-08:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:01bd7cf8-e6a1-4b63-bce6-fb42e01d0951
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:45:03-07:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:e50abce5-1f36-4d9f-bbba-241dc6a20e07
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:45:42-07:00
+
+
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:5206e00c-b360-485d-8869-459d85ae3ce2
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:45:42-07:00
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:110342a5-d3aa-4257-914e-418b9629c650
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-02-08T08:46-07:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:7f75f01b-267e-4630-a1d5-b1afb1a5bda9
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:46:18-07:00
+
+
+
+
+ derived
+ converted from image/jpeg to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:c8692041-63f7-4d21-bec9-e970b76b434e
+ Adobe Photoshop Camera Raw 12.1 (Macintosh)
+ 2020-02-08T08:46:19-07:00
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:f3d1ba30-5073-4364-b39e-b98fb1412d7b
+ Adobe Photoshop 21.0 (Macintosh)
+ 2020-02-08T08:46:48-07:00
+
+
+
+
+ xmp.iid:f3d1ba30-5073-4364-b39e-b98fb1412d7b
+ FE70A58626BB309F945E62BC25DCC69F
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_19_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_19_formatted_compact.xmp
new file mode 100644
index 0000000..13b136b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_19_formatted_compact.xmp
@@ -0,0 +1,422 @@
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ adobe:docid:photoshop:4175d938-93f1-924b-9e9c-eb3dcbc947ca
+ xmp.did:3ff520b3-d1f6-42f9-9906-354073c4a7e9
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_1_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_1_formatted_canonical.xmp
new file mode 100644
index 0000000..ad7e1c7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_1_formatted_canonical.xmp
@@ -0,0 +1,192 @@
+
+
+
+
+ 1
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ sharpen
+ 000000400000003f0000003f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+
+ 0
+ flip
+ ffffffff
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ basecurve
+ gz09eJxjYIAAM6vnNnqyn22E9n235b6aa3cy6rVdRaK9/Y970fYf95bbMzA0QPEoGEqADYnNhCELiVMAudcSGA==
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ clipping
+ 00000000e040f23c4ee4053efce6413f80c75b3f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ clipping
+ 00000000501a1c3d0c5cb53d9a16443feef9503f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ colisa
+ d0ccccbdd0cccc3dd0cc4cbd
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f0000803ecdcc4c3e
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ sharpen
+ 000000400000803f0000003f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f9a99993ecdcc4c3e
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+
+ 0
+ levels
+ 0000000000000000000048420000c842d9899d3dc332d53e8bac483f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ demosaic
+ 0000000000000000000000000100000000000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ cacorrect
+ 32000000
+
+
+
+
+ 12
+ 0
+ 2
+
+
+ dxfoto.ru
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+ 55,54.678955N
+ 36,57.808914E
+ 2.2.0.0
+
+
+ dxfoto|published
+
+
+ 0
+ _MG_9604.CR2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_1_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_1_formatted_compact.xmp
new file mode 100644
index 0000000..f5bae59
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_1_formatted_compact.xmp
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dxfoto.ru
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+
+
+ dxfoto|published
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_2.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_2.xmp
new file mode 100644
index 0000000..0fbf2e5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_2.xmp
@@ -0,0 +1,108 @@
+
+
+
+ FUJIFILM
+ X-T4
+ 1
+ 72/1
+ 72/1
+ 2
+ 2
+ Digital Camera X-T4 Ver1.40
+ 2022-09-27T13:49:21
+ 2022-09-27T13:49:21
+ 3
+
+
+
+ Stefan Oltmann
+
+
+
+
+ John Doe
+
+
+ 10/1000
+ 360/100
+ 1
+
+
+ 2000
+
+
+ 2022-09-27T13:49:21
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+ 32/10
+ 667/100
+ 370/100
+ 317/100
+ 0/100
+ 300/100
+ 5
+ 0
+
+ False
+ 0
+ 0
+ False
+ False
+
+ 3580/100
+
+
+
+
+
+ 1
+ 4416
+ 2944
+ 1882/1
+ 1882/1
+ 3
+ 2
+ 3
+ 1
+ 0
+ 1
+ 0
+ 54
+ 0
+ 2
+ 0
+ 2022-09-27T13:49:21
+ 1337
+ XF18-55mmF2.8-4 R LM OIS
+
+ 2022-09-27T13:49:21.000
+
+ 3
+
+ False
+
+
+ Braut
+ Bräutigam
+ Paar
+ After-Party
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_20.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_20.xmp
new file mode 100644
index 0000000..d84cc9e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_20.xmp
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+
+
+ Standard caption
+
+
+
+
+ 2000
+
+
+
+
+ Standard caption
+
+
+
+
+ Pflanze
+ Ecke
+ People/MacBook
+
+
+
+
+ Pflanze
+ Ecke
+ People/MacBook
+
+
+
+
+
+ Pflanze
+ Ecke
+ People|MacBook
+
+
+
+
+ Pflanze
+ Ecke
+ People|MacBook
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pflanze
+ Ecke
+ MacBook
+
+
+
+
+ Stefan Oltmann
+
+
+
+
+ Stefan Oltmann
+
+
+
+
+ Standard caption
+ Deutsche Beschreibung
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_20_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_20_formatted_canonical.xmp
new file mode 100644
index 0000000..30090be
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_20_formatted_canonical.xmp
@@ -0,0 +1,218 @@
+
+
+
+
+ DEU
+
+
+ Credits City
+ Credits Country
+ Credits State
+
+
+ Wahnbek
+
+
+
+
+
+
+ MacBook
+ 0.289625, 0.411271, 0.583093, 0.502398
+
+
+
+
+
+
+
+
+ Pflanze
+ Ecke
+ People/MacBook
+
+
+ 75
+ <Categories><Category Assigned="1">Pflanze</Category><Category Assigned="1">Ecke</Category><Category Assigned="0">People<Category Assigned="1">MacBook</Category></Category></Categories>
+ Standard caption
+ 4
+
+
+ Stefan Oltmann
+
+
+
+
+ Standard caption
+ Deutsche Beschreibung
+
+
+
+
+ Stefan Oltmann
+
+
+
+
+ Pflanze
+ Ecke
+ MacBook
+
+
+ 0
+ 0
+
+
+ Pflanze
+ Ecke
+ People/MacBook
+
+
+ 370/100
+ 317/100
+ 1
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+ 32/10
+ 0
+ 2022-09-27T13:49:21
+ 0230
+ 0/100
+ 1
+ 1
+ 10/1000
+ 360/100
+ 3
+
+
+ False
+ False
+ 0
+ False
+ 0
+
+
+ 0100
+ 3580/100
+ 54
+ 3
+ 1882/1
+ 1882/1
+ 14/1
+ 0
+ 53,13.1603280N
+ 8,14.3990930E
+ WGS-84
+ 2.0.0.0
+
+
+ 2000
+
+
+ 0
+ 300/100
+ 5
+ 4416
+ 2944
+ 0
+ 1
+ 2
+ 2
+ 667/100
+ 0
+
+
+ Standard caption
+
+
+ 0
+
+
+ Pflanze
+ Ecke
+ People|MacBook
+
+
+
+
+ Pflanze
+ Ecke
+ People|MacBook
+
+
+
+
+
+
+ 4170
+ pixel
+ 6246
+
+
+
+
+
+
+
+
+ 0.502398
+ normalized
+ 0.583093
+ 0.581172
+ 0.66247
+
+
+ MacBook
+ Face
+
+
+
+
+
+
+ Creator Title
+ Rastede
+ Germany
+ 2022-09-27T13:49:21
+ Sample headline
+ Niedersachsen
+ 0
+ FUJIFILM
+ X-T4
+ 1
+ 2
+ 72/1
+ 2
+ 72/1
+ 2022-09-27T13:49:21
+ Digital Camera X-T4 Ver1.40
+ 2022-09-27T13:49:21
+ 2022-09-27T13:49:21
+ 4
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_20_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_20_formatted_compact.xmp
new file mode 100644
index 0000000..c20dd50
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_20_formatted_compact.xmp
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pflanze
+ Ecke
+ People/MacBook
+
+
+
+
+ Stefan Oltmann
+
+
+
+
+ Standard caption
+ Deutsche Beschreibung
+
+
+
+
+ Stefan Oltmann
+
+
+
+
+ Pflanze
+ Ecke
+ MacBook
+
+
+
+
+ Pflanze
+ Ecke
+ People/MacBook
+
+
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+
+
+
+ 2000
+
+
+
+
+ Standard caption
+
+
+
+
+ Pflanze
+ Ecke
+ People|MacBook
+
+
+
+
+ Pflanze
+ Ecke
+ People|MacBook
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_21.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_21.xmp
new file mode 100644
index 0000000..14b4171
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_21.xmp
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Swiper
+
+
+
+
+
+ 75
+
+
+
+ 1.1.8
+ 0/1
+ 0
+ 100mm
+ 220
+
+
+
+ True
+ 0
+ 0
+ -50
+ 0
+ Adobe Standard
+ 98BA1AFA1155D0472068BB57D3655975
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 1
+ 0
+ 0.157865
+ 0.960293
+ 0.197572
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.90
+ 0
+ +16
+ 0
+ True
+ True
+ -100
+ +17
+ 0
+ +58
+ 0
+ +8
+ 0
+ +7
+ -6
+ 0
+ 0
+ LensDefaults
+ +8
+ 0
+ +16
+ 0
+ 0
+ 0
+ 0
+ +4
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ IMG_0081.CR2
+ +9
+ 0
+ 0
+ 0
+ -32
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5750
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 26
+ 77, 54
+ 127, 125
+ 179, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ +20
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+ orange fox walking on street
+
+
+ image/jpeg
+
+
+ 2014
+
+
+
+
+ "fuchs"
+ <HALLO>
+ fox
+ fuchs
+ fuchs = "süß"
+ süßer fuchs
+ was solls
+
+
+
+
+
+
+
+
+
+
+ 2/1
+ 1
+ 0
+ 2018-10-20T15:19:58
+ 0230
+ 0/1
+ 1
+ 1
+ 1/200
+ 2/1
+
+ False
+ False
+ 2
+ False
+ 0
+
+ 100/1
+ 3
+ 1520/1
+ 1520/1
+
+
+ 125
+
+
+ 2/1
+ 5
+ 4390
+ 2927
+ 1
+ 0
+ 2
+ 15303/2002
+ 0
+
+
+
+ 022123002072
+ 100mm
+ 0000000000
+
+
+ 100/1
+ 100/1
+ 0/0
+ 0/0
+
+
+ 125
+ 2
+
+
+
+
+
+ 2927
+ pixel
+ 4390
+
+
+
+
+
+ 0.240097
+ normalized
+ 0.124503
+ 0.404336
+ 0.422313
+
+ mylio_face
+ Swiper
+ Face
+
+
+
+
+
+
+
+ "fuchs", <HALLO>, fox, fuchs, fuchs = "süß", süßer fuchs, was solls
+
+
+
+ 2018-10-20
+
+
+
+
+
+
+ 8
+
+
+ 6
+
+
+ 2014
+
+
+
+
+ orange fox walking on street
+
+
+ 2927
+ 4390
+ Canon
+ Canon EOS 6D
+ 2
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 72/1
+
+
+ 1
+ 1
+
+
+ 72/1
+
+
+
+ 2018-10-20T15:19:58
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+
+ 2023-03-24T09:30:39.600Z
+ 2018-10-20T15:19:58
+ 3
+
+
+
+
+ 0D1AECB577F991DDB109A54A11A712CC
+ 0D1AECB577F991DDB109A54A11A712CC
+
+ ABDEDC0D-076E-471C-9F54-172A6B331E9B
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-03-04T15:08:58-03:30
+
+
+
+ xmp.iid:7A2B77ED-D140-49DA-A311-E8FB2F824E10
+ ABDEDC0D-076E-471C-9F54-172A6B331E9B
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_21_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_21_formatted_canonical.xmp
new file mode 100644
index 0000000..abb27dc
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_21_formatted_canonical.xmp
@@ -0,0 +1,372 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Swiper
+
+
+ 75
+ 1.1.8
+ 0/1
+ 0
+ 100mm
+ 220
+ True
+ 0
+ 0
+ -50
+ 0
+ Adobe Standard
+ 98BA1AFA1155D0472068BB57D3655975
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 1
+ 0
+ 0.157865
+ 0.960293
+ 0.197572
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.90
+ 0
+ +16
+ 0
+ True
+ True
+ -100
+ +17
+ 0
+ +58
+ 0
+ +8
+ 0
+ +7
+ -6
+ 0
+ 0
+ LensDefaults
+ +8
+ 0
+ +16
+ 0
+ 0
+ 0
+ 0
+ +4
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ IMG_0081.CR2
+ +9
+ 0
+ 0
+ 0
+ -32
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5750
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 26
+ 77, 54
+ 127, 125
+ 179, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ +20
+ 0
+ 0
+
+
+
+
+
+
+
+ orange fox walking on street
+
+
+ image/jpeg
+
+
+ 2014
+
+
+
+
+ "fuchs"
+ <HALLO>
+ fox
+ fuchs
+ fuchs = "süß"
+ süßer fuchs
+ was solls
+
+
+
+
+
+
+
+ 2/1
+ 1
+ 0
+ 2018-10-20T15:19:58
+ 0230
+ 0/1
+ 1
+ 1
+ 1/200
+ 2/1
+
+
+ False
+ False
+ 2
+ False
+ 0
+
+
+ 100/1
+ 3
+ 1520/1
+ 1520/1
+
+
+ 125
+
+
+ 2/1
+ 5
+ 4390
+ 2927
+ 1
+ 0
+ 2
+ 15303/2002
+ 0
+ 022123002072
+ 100mm
+ 0000000000
+
+
+ 100/1
+ 100/1
+ 0/0
+ 0/0
+
+
+ 125
+ 2
+
+
+
+
+ 2927
+ pixel
+ 4390
+
+
+
+
+
+
+
+
+ 0.240097
+ normalized
+ 0.124503
+ 0.404336
+ 0.422313
+
+
+ mylio_face
+ Swiper
+ Face
+
+
+
+
+
+
+ "fuchs", <HALLO>, fox, fuchs, fuchs = "süß", süßer fuchs, was solls
+ 2018-10-20
+
+
+ 8
+
+
+ 6
+ 2927
+ 4390
+ Canon
+ Canon EOS 6D
+ 2
+ 72/1
+
+
+ 1
+ 1
+
+
+ 72/1
+ 2018-10-20T15:19:58
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+
+ 2023-03-24T09:30:39.600Z
+ 2018-10-20T15:19:58
+ 3
+
+
+ 0D1AECB577F991DDB109A54A11A712CC
+ 0D1AECB577F991DDB109A54A11A712CC
+
+
+ ABDEDC0D-076E-471C-9F54-172A6B331E9B
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-03-04T15:08:58-03:30
+
+
+
+
+ xmp.iid:7A2B77ED-D140-49DA-A311-E8FB2F824E10
+ ABDEDC0D-076E-471C-9F54-172A6B331E9B
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_21_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_21_formatted_compact.xmp
new file mode 100644
index 0000000..10c0abd
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_21_formatted_compact.xmp
@@ -0,0 +1,350 @@
+
+
+
+
+
+
+
+
+
+
+
+ Swiper
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 26
+ 77, 54
+ 127, 125
+ 179, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+ orange fox walking on street
+
+
+
+
+ 2014
+
+
+
+
+ "fuchs"
+ <HALLO>
+ fox
+ fuchs
+ fuchs = "süß"
+ süßer fuchs
+ was solls
+
+
+
+
+
+
+
+
+
+
+ 125
+
+
+
+
+ 100/1
+ 100/1
+ 0/0
+ 0/0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8
+
+
+
+
+ 1
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_22.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_22.xmp
new file mode 100644
index 0000000..21d139d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_22.xmp
@@ -0,0 +1 @@
+ 0, 0 255, 255 0, 0 255, 255 0, 0 255, 255 0, 0 255, 255 0, 0 255, 255 0, 0 255, 255 0, 0 255, 255 0, 0 255, 255
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_22_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_22_formatted_canonical.xmp
new file mode 100644
index 0000000..2cc2e83
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_22_formatted_canonical.xmp
@@ -0,0 +1,252 @@
+
+
+
+
+ True
+ iPhone 11 Pro Max back dual camera 6mm f/2
+ 17/4 6/1 9/5 2/1
+ True
+ 1
+ 0
+ 0
+ 0
+ Embedded
+ 7C46B99B01D4938E45DB29DB42B23C19
+ +9
+ 0
+ -14
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +2
+ +0.66
+ 46
+ 50
+ 2610145514
+ 1
+ 0
+ 0
+ False
+ True
+ -97
+ 0
+ 0
+ +100
+ 0
+ +100
+ 0
+ -6
+ +100
+ -6
+ +3
+ 0
+ 1
+ LensDefaults
+ 0
+ 0
+ +59
+ 0
+ +76
+ 0
+ 0
+ +35
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ +13
+ 50
+ 50
+ 0
+ 1
+ 11.0
+ 0
+ 0
+ +11
+ 0
+ 0
+ -30
+ 0
+ -1
+ 0
+ -100
+ -45
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +13
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 12.2
+ +8
+ 0
+ Custom
+ +51
+ image/jpeg
+
+
+
+
+ 4032
+ pixel
+ 3023
+
+
+
+
+
+
+
+
+ 0.11299999803304672
+ normalized
+ 0.15800000727176666
+ 0.57699999213218689
+ 0.65649998188018799
+
+
+ Focus
+
+
+
+
+
+
+ 0.113000
+ normalized
+ 0.158000
+ 0.577000
+ 0.656500
+
+
+ Focus
+
+
+
+
+
+
+ 2020-02-17T14:47:46.950
+ 2020-02-17T14:47:46.950
+ 13.3.1
+ 2020-02-20T01:11:51Z
+ 2020-02-20T01:11:51Z
+ xmp.did:93cd3a00-3cf7-400d-a52d-dd6912fe9eef
+
+
+
+
+ derived
+ saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:93cd3a00-3cf7-400d-a52d-dd6912fe9eef
+ Adobe Lightroom 5.2.0 (iOS)
+ 2020-02-20T01:11:51Z
+
+
+
+
+ xmp.iid:93cd3a00-3cf7-400d-a52d-dd6912fe9eef
+ xmp.did:93cd3a00-3cf7-400d-a52d-dd6912fe9eef
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_22_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_22_formatted_compact.xmp
new file mode 100644
index 0000000..6bbf434
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_22_formatted_compact.xmp
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_23.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_23.xmp
new file mode 100644
index 0000000..9f8ff2f
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_23.xmp
@@ -0,0 +1,342 @@
+
+
+
+
+
+
+ bird
+ animal
+
+
+
+
+ penguins on snow covered fields during daytime
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ penguins on snow covered fields during daytime
+
+
+
+
+ penguins on snow covered fields during daytime
+
+
+
+
+ 200
+
+
+
+
+
+ bird
+ animal
+
+
+
+
+ bird
+ animal
+
+
+
+
+ bird
+ animal
+
+
+
+
+ bird
+ animal
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_23_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_23_formatted_canonical.xmp
new file mode 100644
index 0000000..64a55e2
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_23_formatted_canonical.xmp
@@ -0,0 +1,353 @@
+
+
+
+
+
+
+ bird
+ animal
+
+
+ 25
+ <Categories><Category Assigned="1">bird</Category><Category Assigned="1">animal</Category></Categories>
+ penguins on snow covered fields during daytime
+ 2
+ True
+ True
+ XF55-200mmF3.5-4.8 R LM OIS
+ 5500/100 20000/100 350/100 480/100
+ 48A00942
+ True
+ True
+ 1
+ 134348800
+ -23
+ 0
+ 0
+ Adobe Standard
+ 0
+ 25
+ 50
+ 50
+ -14
+ False
+ 0
+ 0.890563
+ 1
+ 3
+ 0.135777
+ 1
+ 0.161375
+ 3
+ 2
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.02
+ 0
+ 0
+ 0
+ True
+ True
+ -56
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ 4D6BC5CC0FDB88283CF51E311C1128C5
+ 100
+ 1
+ Fujifilm FinePix X100.lcp
+ Adobe (Fujifilm FinePix X100)
+ Custom
+ 100
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ -33
+ 75
+ 0
+ +61
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 6.7
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +29
+ 25
+ 0
+ +1.0
+ 40
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4800
+ +7
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 9.12
+ +8
+ 0
+ As Shot
+ +33
+
+
+ penguins on snow covered fields during daytime
+
+
+ image/jpeg
+
+
+ bird
+ animal
+
+
+ 0
+ 0
+
+
+ bird
+ animal
+
+
+ 4970854/1000000
+ 1009/100
+ 65535
+ 0
+ 2019-02-14T09:33:24
+ 0230
+ 0/100
+ 0
+ 3
+ 1/1400
+ 56/10
+ 3
+
+
+ False
+ False
+ 2
+ False
+ 0
+
+
+ 11030/100
+ 165
+ 3
+ 68269609/32768
+ 68269609/32768
+ 0/1
+ 0
+ 64,8.7990590S
+ 61,42.8767800W
+ WGS-84
+ 2.0.0.0
+
+
+ 200
+
+
+ 0
+ 360/100
+ 5
+ 0
+ 1
+ 2
+ 0
+ 10451211/1000000
+ 0
+
+
+ penguins on snow covered fields during daytime
+
+
+ 0
+ FUJIFILM
+ XF55-200mmF3.5-4.8 R LM OIS
+
+
+ bird
+ animal
+
+
+
+
+ bird
+ animal
+
+
+ 2019-02-14T09:33:24
+ 0
+ FUJIFILM
+ X-E2
+ 2
+ 240/1
+ 240/1
+ 2019-02-14T09:33:24
+ Adobe Photoshop Lightroom 6.12 (Macintosh)
+ 2019-02-14T09:33:24
+ 2019-02-14T09:33:24
+ 2
+
+
+ 4D36C1932CD59E82A7EBF1F71673E32A
+ xmp.iid:4303602c-4be1-4d7a-bf42-febdc65bc7c0
+ 4D36C1932CD59E82A7EBF1F71673E32A
+
+
+ xmp.did:533b1eb4-1102-41af-8366-f7c8b9cb6c28
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:de69c16c-8f51-42c4-953f-99e24ac5db59
+ Adobe Photoshop Camera Raw 10.4
+ 2019-02-14T10:18:25-05:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:4303602c-4be1-4d7a-bf42-febdc65bc7c0
+ Adobe Photoshop Camera Raw 10.4 (Macintosh)
+ 2019-02-14T10:31:17-05:00
+
+
+
+
+ derived
+ converted from image/x-fuji-raw to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:533b1eb4-1102-41af-8366-f7c8b9cb6c28
+ Adobe Photoshop Lightroom 6.12 (Macintosh)
+ 2019-02-28T17:44:45-05:00
+
+
+
+
+ xmp.iid:533b1eb4-1102-41af-8366-f7c8b9cb6c28
+ 4D36C1932CD59E82A7EBF1F71673E32A
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_23_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_23_formatted_compact.xmp
new file mode 100644
index 0000000..d886909
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_23_formatted_compact.xmp
@@ -0,0 +1,335 @@
+
+
+
+
+
+
+ bird
+ animal
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ penguins on snow covered fields during daytime
+
+
+
+
+ bird
+ animal
+
+
+
+
+ bird
+ animal
+
+
+
+
+
+ 200
+
+
+
+
+ penguins on snow covered fields during daytime
+
+
+
+
+ bird
+ animal
+
+
+
+
+ bird
+ animal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_24.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_24.xmp
new file mode 100644
index 0000000..5263a4b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_24.xmp
@@ -0,0 +1,330 @@
+
+
+
+
+
+
+ puddle
+
+
+
+
+ selective focus photography of dirt road near red bridge
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ selective focus photography of dirt road near red bridge
+
+
+
+
+ selective focus photography of dirt road near red bridge
+
+
+
+
+ 2000
+
+
+
+
+
+ puddle
+
+
+
+
+ puddle
+
+
+
+
+ puddle
+
+
+
+
+ puddle
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_24_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_24_formatted_canonical.xmp
new file mode 100644
index 0000000..b6c55e4
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_24_formatted_canonical.xmp
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+ puddle
+
+
+ 50
+ <Categories><Category Assigned="1">puddle</Category></Categories>
+ selective focus photography of dirt road near red bridge
+ 3
+ 3484
+ True
+ FE 24-70mm F2.8 GM
+ 33421/32768 -86619351/1073741824 53554433/1073741824 3197770/1073741824
+ 240/10 700/10 28/10 28/10
+ True
+ 0
+ 0
+ 0
+ 0
+ Embedded
+ 54650A341B5B5CCAE8442D0B43A92BCE
+ 0
+ 0
+ 0
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ 0
+ 0
+ As Shot
+ 0
+
+
+ selective focus photography of dirt road near red bridge
+
+
+ image/jpeg
+
+
+ puddle
+
+
+ 0
+ 0
+
+
+ puddle
+
+
+ 2970854/1000000
+ -18086/2560
+ 1
+ 0
+ 0
+ 2019-02-08T09:19:15
+ 16/16
+ 0231
+ 0/10
+ 1
+ 1
+ 2/1
+ 28/10
+ 3
+
+
+ False
+ False
+ 2
+ False
+ 0
+
+
+ 250/10
+ 25
+ 3
+ 70924134/32768
+ 70924134/32768
+
+
+ 2000
+
+
+ 0
+ 760/256
+ 5
+ 0
+ 0
+ 1
+ 0
+ -1/1
+
+
+ selective focus photography of dirt road near red bridge
+
+
+ 1
+ FE 24-70mm F2.8 GM
+
+
+ puddle
+
+
+
+
+ puddle
+
+
+ Perspective
+ 0.694008
+ 0.500189
+ 0.333668
+ 0
+ PM5
+ 0:0:1:003484
+ False
+ 2019-02-08T09:19:15
+ 0
+ SONY
+ ILCE-7RM3
+ 2
+ 300/1
+ 300/1
+ 2019-02-08T09:19:15
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-02-08T09:19:15
+ 2019-02-08T09:19:15
+ 3
+
+
+ adobe:docid:photoshop:d0651ba9-a4c9-9241-b299-09681a5a7d8b
+ xmp.iid:d9553137-4619-4a12-aa17-e0c3673b3245
+ 50A99023732AE18ACBE2BD72D5B1472A
+
+
+ xmp.did:137f1c65-0920-43c8-b073-37401ff6b2c1
+
+
+
+
+ derived
+ converted from image/x-sony-arw to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:c97b8478-71b5-4e2f-8e94-d56951c0e16a
+ Adobe Photoshop Camera Raw 11.0 (Macintosh)
+ 2019-02-10T14:29:04-08:00
+
+
+
+
+ saved
+ /
+ xmp.iid:d9553137-4619-4a12-aa17-e0c3673b3245
+ Adobe Photoshop CC 2019 (Macintosh)
+ 2019-02-10T15:00:27-08:00
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:137f1c65-0920-43c8-b073-37401ff6b2c1
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-02-11T00:15:52-05:00
+
+
+
+
+ xmp.iid:137f1c65-0920-43c8-b073-37401ff6b2c1
+ 50A99023732AE18ACBE2BD72D5B1472A
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_24_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_24_formatted_compact.xmp
new file mode 100644
index 0000000..8770b24
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_24_formatted_compact.xmp
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+ puddle
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ selective focus photography of dirt road near red bridge
+
+
+
+
+ puddle
+
+
+
+
+ puddle
+
+
+
+
+
+ 2000
+
+
+
+
+ selective focus photography of dirt road near red bridge
+
+
+
+
+ puddle
+
+
+
+
+ puddle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_25.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_25.xmp
new file mode 100644
index 0000000..8292395
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_25.xmp
@@ -0,0 +1,424 @@
+
+
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ selective focus photography of hedgehog eating on green grass field
+
+
+
+
+ 871ABA18D31FD8E7DBCC53B9FE0224AF
+ FD7181A14C33CB21D9F24DFA009838BB
+ adobe:docid:photoshop:12be8701-ee83-11dc-8fa4-839a83b75f76
+ adobe:docid:photoshop:8e134c0c-d987-11e1-8180-e5485f4b3dd6
+ adobe:docid:photoshop:b431105b-06cd-11e8-871a-e51073a125a5
+ uuid:7482E5E1C880E2118C5CA233DA119F04
+ uuid:B817E0D43CEFDB11B569A5BC76F1A641
+ uuid:F29EE4DF7EE8DC118DF8881DDBB15AA6
+ uuid:F5473D8EC2CFDD11A615E91EF51C8E6B
+ uuid:e6789bf8-372e-4f3f-9a32-51adbe529a51
+ xmp.did:026DA6464E99E5118955C48E323C1370
+ xmp.did:04BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:05BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:06BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:15E498DDD1BCE3118479C8D66754C651
+ xmp.did:1BE35DF84721E411BCDC879249A1E379
+ xmp.did:1CE35DF84721E411BCDC879249A1E379
+ xmp.did:1d863f65-8cb9-204c-a7f2-46ae0b82ab5d
+ xmp.did:1edd63cf-63f1-be43-8eb6-28739a04fc34
+ xmp.did:2A536F8FF323E4119256983A0A8E27F4
+ xmp.did:2B536F8FF323E4119256983A0A8E27F4
+ xmp.did:3277AEDEF242E611A6CA9D14FBC23D21
+ xmp.did:345F41AC61FFE511984BD03C261E519F
+ xmp.did:38E03B705845E4119D63AC8F7240C674
+ xmp.did:676074CE1267E2118E9F83D25945345A
+ xmp.did:72319FB1F844E41190628966A279BD7B
+ xmp.did:7DAF784D0E67E211B5DF870CB1273C7A
+ xmp.did:93B00518BC9AE411B0F7CA6000FB75A5
+ xmp.did:9D0D9797999EE41193BFE3E162C5B50C
+ xmp.did:A65843342923E411A15FDD5EB829D6CF
+ xmp.did:B644D45FBBBCE1119D66D9D8E4A4AB2E
+ xmp.did:B7A1306DA368E21194B2DCB767C38B70
+ xmp.did:B844D45FBBBCE1119D66D9D8E4A4AB2E
+ xmp.did:BA19A591633FE6118B589AE61772D815
+ xmp.did:CD0B892B2DA7E411B204F4CC894F2836
+ xmp.did:D35C2DD9103FE611887695DCDE94486C
+ xmp.did:E2465DCDA944E411B6E28C01EC3A081F
+ xmp.did:E6465DCDA944E411B6E28C01EC3A081F
+ xmp.did:E679195AAFC3E511B30DF07EB0FADE24
+ xmp.did:E7368B9DDBD6E4118EDED9318DFC5458
+ xmp.did:FC05A09BD220E4118462AF7374FB7815
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 32, 16
+ 64, 50
+ 128, 128
+ 192, 202
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ selective focus photography of hedgehog eating on green grass field
+
+
+
+
+ selective focus photography of hedgehog eating on green grass field
+
+
+
+
+ 100
+
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_25_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_25_formatted_canonical.xmp
new file mode 100644
index 0000000..706823b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_25_formatted_canonical.xmp
@@ -0,0 +1,454 @@
+
+
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+ 75
+ <Categories><Category Assigned="1">mammal</Category><Category Assigned="1">rodent</Category><Category Assigned="1">hedgehog</Category><Category Assigned="1">porcupine</Category><Category Assigned="1">animal</Category></Categories>
+ selective focus photography of hedgehog eating on green grass field
+ 4
+ True
+ 1.1.0
+ 0/1
+ 0
+ True
+ EF50mm f/1.8 II
+ 29
+ 50/1 50/1 0/0 0/0
+ 1850746011
+ True
+ True
+ 1
+ 0
+ 0
+ 0
+ Adobe Standard
+ 1564A98C46E40037DB79C8C97BDEDC38
+ +33
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +5
+ -1.00
+ 0
+ 0
+ 0
+ False
+ True
+ -39
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ 91738A0C4927BAFDC05FE184C12E1434
+ 100
+ 1
+ Canon EOS 5D Mark II (Canon EF 50mm f1.8 II) - RAW.lcp
+ Adobe (Canon EF 50mm f/1.8 II)
+ LensDefaults
+ 100
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 34
+ +7
+ 75
+ 0
+ +16
+ 50
+ 25
+ -14
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ -8
+ 50
+ 0
+ 50
+ 0
+ 1
+ 6.7
+ _MG_2593.CR2
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +3
+ 25
+ 74
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4900
+ +4
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Medium Contrast
+ Strong Contrast
+
+
+ 0, 0
+ 32, 16
+ 64, 50
+ 128, 128
+ 192, 202
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 9.12
+ +19
+ 0
+ As Shot
+ 0
+
+
+ selective focus photography of hedgehog eating on green grass field
+
+
+ image/jpeg
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+ 0
+ 0
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+ 3356144/1000000
+ 65535
+ 0
+ 2018-05-17T19:44:17
+ 0221
+ 0/1
+ 1
+ 1
+ 1/160
+ 32/10
+
+
+ False
+ False
+ 2
+ False
+ 0
+
+
+ 50/1
+ 2
+ 4752000/894
+ 3168000/593
+
+
+ 100
+
+
+ 175/100
+ 5
+ 4660
+ 3106
+ 0
+ 7321928/1000000
+
+
+ selective focus photography of hedgehog eating on green grass field
+
+
+ 1
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+ 3
+ 2018-05-17T19:44:17
+
+
+ 871ABA18D31FD8E7DBCC53B9FE0224AF
+ FD7181A14C33CB21D9F24DFA009838BB
+ adobe:docid:photoshop:12be8701-ee83-11dc-8fa4-839a83b75f76
+ adobe:docid:photoshop:8e134c0c-d987-11e1-8180-e5485f4b3dd6
+ adobe:docid:photoshop:b431105b-06cd-11e8-871a-e51073a125a5
+ uuid:7482E5E1C880E2118C5CA233DA119F04
+ uuid:B817E0D43CEFDB11B569A5BC76F1A641
+ uuid:F29EE4DF7EE8DC118DF8881DDBB15AA6
+ uuid:F5473D8EC2CFDD11A615E91EF51C8E6B
+ uuid:e6789bf8-372e-4f3f-9a32-51adbe529a51
+ xmp.did:026DA6464E99E5118955C48E323C1370
+ xmp.did:04BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:05BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:06BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:15E498DDD1BCE3118479C8D66754C651
+ xmp.did:1BE35DF84721E411BCDC879249A1E379
+ xmp.did:1CE35DF84721E411BCDC879249A1E379
+ xmp.did:1d863f65-8cb9-204c-a7f2-46ae0b82ab5d
+ xmp.did:1edd63cf-63f1-be43-8eb6-28739a04fc34
+ xmp.did:2A536F8FF323E4119256983A0A8E27F4
+ xmp.did:2B536F8FF323E4119256983A0A8E27F4
+ xmp.did:3277AEDEF242E611A6CA9D14FBC23D21
+ xmp.did:345F41AC61FFE511984BD03C261E519F
+ xmp.did:38E03B705845E4119D63AC8F7240C674
+ xmp.did:676074CE1267E2118E9F83D25945345A
+ xmp.did:72319FB1F844E41190628966A279BD7B
+ xmp.did:7DAF784D0E67E211B5DF870CB1273C7A
+ xmp.did:93B00518BC9AE411B0F7CA6000FB75A5
+ xmp.did:9D0D9797999EE41193BFE3E162C5B50C
+ xmp.did:A65843342923E411A15FDD5EB829D6CF
+ xmp.did:B644D45FBBBCE1119D66D9D8E4A4AB2E
+ xmp.did:B7A1306DA368E21194B2DCB767C38B70
+ xmp.did:B844D45FBBBCE1119D66D9D8E4A4AB2E
+ xmp.did:BA19A591633FE6118B589AE61772D815
+ xmp.did:CD0B892B2DA7E411B204F4CC894F2836
+ xmp.did:D35C2DD9103FE611887695DCDE94486C
+ xmp.did:E2465DCDA944E411B6E28C01EC3A081F
+ xmp.did:E6465DCDA944E411B6E28C01EC3A081F
+ xmp.did:E679195AAFC3E511B30DF07EB0FADE24
+ xmp.did:E7368B9DDBD6E4118EDED9318DFC5458
+ xmp.did:FC05A09BD220E4118462AF7374FB7815
+
+
+ Adobe RGB (1998)
+ 0
+ Canon
+ Canon EOS 500D
+ 1
+ 2
+ 3000000/10000
+ 3000000/10000
+ 2018-05-17T19:44:17
+ Adobe Photoshop CC 2017 (Windows)
+ 2018-05-17T19:44:17
+ 2018-05-17T19:44:17
+ 4
+
+
+ xmp.did:1d863f65-8cb9-204c-a7f2-46ae0b82ab5d
+ xmp.iid:538f1514-22d7-f448-8555-37cf07bc0117
+ CBD5B8C3D30C0B13434B30B971EBF9DA
+
+
+ adobe:docid:photoshop:f3167708-5a83-11e8-86f4-8e5f4d440010
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:1edd63cf-63f1-be43-8eb6-28739a04fc34
+ Adobe Photoshop Camera Raw 9.12 (Windows)
+ 2018-05-18T11:54:23+02:00
+
+
+
+
+ saved
+ /
+ xmp.iid:213d0dd5-b1d2-ea41-bd05-7e1fea066277
+ Adobe Photoshop CC 2017 (Windows)
+ 2018-05-18T12:11:10+02:00
+
+
+
+
+ converted
+ from image/tiff to application/vnd.adobe.photoshop
+
+
+
+
+ derived
+ converted from image/tiff to application/vnd.adobe.photoshop
+
+
+
+
+ saved
+ /
+ xmp.iid:1d863f65-8cb9-204c-a7f2-46ae0b82ab5d
+ Adobe Photoshop CC 2017 (Windows)
+ 2018-05-18T12:11:10+02:00
+
+
+
+
+ saved
+ /
+ xmp.iid:538f1514-22d7-f448-8555-37cf07bc0117
+ Adobe Photoshop CC 2017 (Windows)
+ 2018-05-18T12:12:44+02:00
+
+
+
+
+ converted
+ from application/vnd.adobe.photoshop to image/jpeg
+
+
+
+
+ derived
+ converted from application/vnd.adobe.photoshop to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:a330c3fb-4e2f-f948-bd00-04e86112dfd9
+ Adobe Photoshop CC 2017 (Windows)
+ 2018-05-18T12:12:44+02:00
+
+
+
+
+ xmp.iid:a330c3fb-4e2f-f948-bd00-04e86112dfd9
+ CBD5B8C3D30C0B13434B30B971EBF9DA
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_25_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_25_formatted_compact.xmp
new file mode 100644
index 0000000..988cc7e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_25_formatted_compact.xmp
@@ -0,0 +1,418 @@
+
+
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 32, 16
+ 64, 50
+ 128, 128
+ 192, 202
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ selective focus photography of hedgehog eating on green grass field
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+
+ 100
+
+
+
+
+ selective focus photography of hedgehog eating on green grass field
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ mammal
+ rodent
+ hedgehog
+ porcupine
+ animal
+
+
+
+
+ 871ABA18D31FD8E7DBCC53B9FE0224AF
+ FD7181A14C33CB21D9F24DFA009838BB
+ adobe:docid:photoshop:12be8701-ee83-11dc-8fa4-839a83b75f76
+ adobe:docid:photoshop:8e134c0c-d987-11e1-8180-e5485f4b3dd6
+ adobe:docid:photoshop:b431105b-06cd-11e8-871a-e51073a125a5
+ uuid:7482E5E1C880E2118C5CA233DA119F04
+ uuid:B817E0D43CEFDB11B569A5BC76F1A641
+ uuid:F29EE4DF7EE8DC118DF8881DDBB15AA6
+ uuid:F5473D8EC2CFDD11A615E91EF51C8E6B
+ uuid:e6789bf8-372e-4f3f-9a32-51adbe529a51
+ xmp.did:026DA6464E99E5118955C48E323C1370
+ xmp.did:04BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:05BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:06BFFCF3DA20E411A80BEFF385C528EF
+ xmp.did:15E498DDD1BCE3118479C8D66754C651
+ xmp.did:1BE35DF84721E411BCDC879249A1E379
+ xmp.did:1CE35DF84721E411BCDC879249A1E379
+ xmp.did:1d863f65-8cb9-204c-a7f2-46ae0b82ab5d
+ xmp.did:1edd63cf-63f1-be43-8eb6-28739a04fc34
+ xmp.did:2A536F8FF323E4119256983A0A8E27F4
+ xmp.did:2B536F8FF323E4119256983A0A8E27F4
+ xmp.did:3277AEDEF242E611A6CA9D14FBC23D21
+ xmp.did:345F41AC61FFE511984BD03C261E519F
+ xmp.did:38E03B705845E4119D63AC8F7240C674
+ xmp.did:676074CE1267E2118E9F83D25945345A
+ xmp.did:72319FB1F844E41190628966A279BD7B
+ xmp.did:7DAF784D0E67E211B5DF870CB1273C7A
+ xmp.did:93B00518BC9AE411B0F7CA6000FB75A5
+ xmp.did:9D0D9797999EE41193BFE3E162C5B50C
+ xmp.did:A65843342923E411A15FDD5EB829D6CF
+ xmp.did:B644D45FBBBCE1119D66D9D8E4A4AB2E
+ xmp.did:B7A1306DA368E21194B2DCB767C38B70
+ xmp.did:B844D45FBBBCE1119D66D9D8E4A4AB2E
+ xmp.did:BA19A591633FE6118B589AE61772D815
+ xmp.did:CD0B892B2DA7E411B204F4CC894F2836
+ xmp.did:D35C2DD9103FE611887695DCDE94486C
+ xmp.did:E2465DCDA944E411B6E28C01EC3A081F
+ xmp.did:E6465DCDA944E411B6E28C01EC3A081F
+ xmp.did:E679195AAFC3E511B30DF07EB0FADE24
+ xmp.did:E7368B9DDBD6E4118EDED9318DFC5458
+ xmp.did:FC05A09BD220E4118462AF7374FB7815
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_26.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_26.xmp
new file mode 100644
index 0000000..9ba718e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_26.xmp
@@ -0,0 +1,316 @@
+
+
+
+
+
+
+ White-tailed eagle Photo taken on photoworkshop. Animal in huma
+
+
+
+
+ animal
+
+
+
+
+ ASCII
+
+
+
+
+ ASCII
+
+
+
+
+ 1600
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 44
+ 84, 67
+ 195, 192
+ 255, 231
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ ASCII
+
+
+
+
+ animal
+
+
+
+
+ animal
+
+
+
+
+ animal
+
+
+
+
+ animal
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_26_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_26_formatted_canonical.xmp
new file mode 100644
index 0000000..c511cc8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_26_formatted_canonical.xmp
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+ animal
+
+
+ 50
+ White-tailed eagle Photo taken on photoworkshop. Animal in huma
+ <Categories><Category Assigned="1">animal</Category></Categories>
+ ASCII
+ 3
+ 708/100
+ 621
+ 55.0-200.0 mm f/4.0-5.6
+ 167
+ 550/10 2000/10 40/10 56/10
+ 8919190
+ True
+ 0
+ 134348800
+ +12
+ 0
+ 0
+ Embedded
+ +10
+ 0
+ +5
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ -0.12
+ 4
+ 37
+ 1526252090
+ 25
+ 0
+ 0
+ False
+ True
+ -100
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 2
+ 35
+ 13
+ +3
+ 75
+ +24
+ +5
+ 50
+ 25
+ -15
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ -2
+ 45
+ 0
+ 35
+ +38
+ 1
+ 6.7
+ Japan20170408_0175.jpg
+ 0
+ 0
+ -12
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ -19
+ 12
+ 0
+ +1.0
+ 29
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 44
+ 84, 67
+ 195, 192
+ 255, 231
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 9.0
+ +38
+ 0
+ As Shot
+ +31
+
+
+ ASCII
+
+
+ image/jpeg
+
+
+ animal
+
+
+
+
+ White-tailed eagle Photo taken on photoworkshop. Animal in huma
+
+
+ 0
+ 0
+
+
+ animal
+
+
+ 4/1
+ 1
+ 0
+ 0
+ 2017-11-12T10:48:04
+ 1/1
+ 0230
+ 0/1
+ 1
+ 1
+ 1/400
+ 4/1
+
+
+ False
+ False
+ 0
+ False
+ 0
+
+
+ 82
+ 55/1
+ 82
+ 2
+
+
+ 1600
+
+
+ 0
+ 4/1
+ 5
+ 2304
+ 1536
+ 0
+ 0
+ 2
+ 0
+ 11747/1359
+ 0
+ 0
+ 5
+ 5
+
+
+ ASCII
+
+
+ 0
+
+
+ animal
+
+
+
+
+ animal
+
+
+ 2017-11-12T10:48:04
+ 0
+ NIKON CORPORATION
+ NIKON D3300
+ 1
+ 2
+ 240.000000
+ 240.000000
+ 2017-11-12T10:48:04
+ Adobe Photoshop Lightroom 6.0 (Windows)
+ 2017-11-12T10:48:04
+ 2017-11-12T10:48:04
+ 3
+
+
+ 78EA45ED1A662131AC3C8190747C495D
+ 78EA45ED1A662131AC3C8190747C495D
+
+
+ xmp.did:054890f4-cf40-3e4e-aec2-15f6801d0298
+
+
+
+
+ derived
+ saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:054890f4-cf40-3e4e-aec2-15f6801d0298
+ Adobe Photoshop Lightroom 6.0 (Windows)
+ 2017-05-03T10:40:16-06:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:a4a41d2e-1764-da48-ae11-7d74e776b25d
+ Adobe Photoshop Lightroom 6.0 (Windows)
+ 2017-05-03T10:40:18-06:00
+
+
+
+
+ xmp.iid:a4a41d2e-1764-da48-ae11-7d74e776b25d
+ 78EA45ED1A662131AC3C8190747C495D
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_26_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_26_formatted_compact.xmp
new file mode 100644
index 0000000..4ac90b0
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_26_formatted_compact.xmp
@@ -0,0 +1,308 @@
+
+
+
+
+
+
+ animal
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 44
+ 84, 67
+ 195, 192
+ 255, 231
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ ASCII
+
+
+
+
+ animal
+
+
+
+
+ White-tailed eagle Photo taken on photoworkshop. Animal in huma
+
+
+
+
+ animal
+
+
+
+
+
+ 1600
+
+
+
+
+ ASCII
+
+
+
+
+ animal
+
+
+
+
+ animal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_27.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_27.xmp
new file mode 100644
index 0000000..4475cfd
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_27.xmp
@@ -0,0 +1,284 @@
+
+
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ brown butterfly on orange petaled flower
+
+
+
+
+
+ saved
+ xmp.iid:7401f5df-4c24-c749-bc58-0b3929d876d1
+ 2012-11-20T21:40:25+08:00
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ /metadata
+ saved
+ xmp.iid:f64c2c89-48d8-ca43-9309-ddfc18bbd4fc
+ 2012-11-20T21:40:26+08:00
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ /metadata
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ brown butterfly on orange petaled flower
+
+
+
+
+ brown butterfly on orange petaled flower
+
+
+
+
+ 400
+
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_27_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_27_formatted_canonical.xmp
new file mode 100644
index 0000000..21e026c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_27_formatted_canonical.xmp
@@ -0,0 +1,289 @@
+
+
+
+
+
+
+ invertebrate
+ cosmos
+
+
+ 25
+ <Categories><Category Assigned="1">invertebrate</Category><Category Assigned="1">cosmos</Category></Categories>
+ brown butterfly on orange petaled flower
+ 2
+ 350/100
+ 1.0.1
+ 0/1
+ 0
+ EF400mm f/5.6L USM
+ 172
+ 400/1 400/1 0/0 0/0
+ 0000000000
+ 102025013293
+ True
+ 1
+ 0
+ 0
+ 0
+ Embedded
+ +30
+ 0
+ 0
+ False
+ 0
+ 0.83586
+ 0
+ 2470
+ 0.088384
+ 0.803031
+ 0.121213
+ 0
+ 3705
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ -0.60
+ 0
+ 0
+ 0
+ True
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+ 0
+ 0
+ 0
+ 0
+ +33
+ 0
+ 0
+ 0
+ 0
+ 50
+ 17
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 6.7
+ 0
+ 0
+ +7
+ 0
+ 0
+ 0
+ 0
+ +36
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 7.2
+ +25
+ 0
+ As Shot
+ 0
+
+
+ brown butterfly on orange petaled flower
+
+
+ image/jpeg
+
+
+ invertebrate
+ cosmos
+
+
+ 0
+ 0
+
+
+ invertebrate
+ cosmos
+
+
+ 4970854/1000000
+ 2011-06-26T08:16:55
+ 0230
+ 0/1
+ 1
+ 1
+ 1/640
+ 56/10
+
+
+ False
+ False
+ 1
+ False
+ 0
+
+
+ 400/1
+ 2
+ 5184000/905
+ 3456000/595
+ 0/1
+ 0
+ 22,48.0988000N
+ 120,17.1530990E
+ WGS-84
+ 2.0.0.0
+
+
+ 400
+
+
+ 255
+ 5/1
+ 5
+ 0
+ 9321928/1000000
+
+
+ brown butterfly on orange petaled flower
+
+
+ 1
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ invertebrate
+ cosmos
+
+
+ 2011-06-26T08:16:55
+ 0
+ Canon
+ Canon EOS REBEL T3i
+ 3
+ 240/1
+ 240/1
+ 2011-06-26T08:16:55
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ 2011-06-26T08:16:55
+ 2011-06-26T08:16:55
+ 2
+
+
+ 5A955C431B805E6BA1D276896A7DF8D0
+ xmp.iid:f64c2c89-48d8-ca43-9309-ddfc18bbd4fc
+ 5A955C431B805E6BA1D276896A7DF8D0
+
+
+ xmp.did:db26b988-3004-0146-a322-b2eefc779823
+
+
+ saved
+ xmp.iid:7401f5df-4c24-c749-bc58-0b3929d876d1
+ 2012-11-20T21:40:25+08:00
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ /metadata
+ saved
+ xmp.iid:f64c2c89-48d8-ca43-9309-ddfc18bbd4fc
+ 2012-11-20T21:40:26+08:00
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ /metadata
+
+
+ derived
+ saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:db26b988-3004-0146-a322-b2eefc779823
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ 2012-11-20T21:52:09+08:00
+
+
+
+
+ xmp.iid:db26b988-3004-0146-a322-b2eefc779823
+ 5A955C431B805E6BA1D276896A7DF8D0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_27_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_27_formatted_compact.xmp
new file mode 100644
index 0000000..591a781
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_27_formatted_compact.xmp
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ brown butterfly on orange petaled flower
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+
+ 400
+
+
+
+
+ brown butterfly on orange petaled flower
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+ invertebrate
+ cosmos
+
+
+
+
+
+ saved
+ xmp.iid:7401f5df-4c24-c749-bc58-0b3929d876d1
+ 2012-11-20T21:40:25+08:00
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ /metadata
+ saved
+ xmp.iid:f64c2c89-48d8-ca43-9309-ddfc18bbd4fc
+ 2012-11-20T21:40:26+08:00
+ Adobe Photoshop Lightroom 4.2 (Windows)
+ /metadata
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_28.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_28.xmp
new file mode 100644
index 0000000..097cd2e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_28.xmp
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+ time lapse photography of fire
+
+
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+
+
+ time lapse photography of fire
+
+
+
+
+ 3200
+
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ time lapse photography of fire
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_28_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_28_formatted_canonical.xmp
new file mode 100644
index 0000000..920d95c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_28_formatted_canonical.xmp
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+ people
+ person
+ human
+
+
+ 25
+ <Categories><Category Assigned="1">people</Category><Category Assigned="1">person</Category><Category Assigned="1">human</Category></Categories>
+ time lapse photography of fire
+ 2
+
+
+ time lapse photography of fire
+
+
+
+
+ people
+ person
+ human
+
+
+ 0
+ 0
+
+
+ people
+ person
+ human
+
+
+ 159/32
+ 1
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+ 3/1
+ 0
+ 2017-11-12T21:41:48
+ 6000/6000
+ 0230
+ 0/3
+ 0
+ 4
+ 1/4000
+ 56/10
+ 3
+
+
+ False
+ False
+ 2
+ False
+ 0
+
+
+ 0100
+ 55/1
+ 2
+ 2304000/879
+ 1536000/586
+
+
+ 3200
+
+
+ 5
+ 2304
+ 1536
+ 0
+ 2
+ 383/32
+
+
+ time lapse photography of fire
+
+
+ 1
+
+
+ people
+ person
+ human
+
+
+
+
+ people
+ person
+ human
+
+
+ 2017-11-12T21:41:48
+ 0
+ Canon
+ Canon EOS M3
+ 1
+ 2
+ 180/1
+ 2
+ 180/1
+ 2017-11-12T21:41:48
+ 2017-11-12T21:41:48
+ 2017-11-12T21:41:48
+ 2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_28_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_28_formatted_compact.xmp
new file mode 100644
index 0000000..5ad692f
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_28_formatted_compact.xmp
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ time lapse photography of fire
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+
+
+
+ 3200
+
+
+
+
+ time lapse photography of fire
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+ people
+ person
+ human
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_29.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_29.xmp
new file mode 100644
index 0000000..93d9a5e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_29.xmp
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 1, 38
+ 55, 60
+ 118, 103
+ 189, 171
+ 255, 234
+
+
+
+
+ 0, 4
+ 22, 19
+ 66, 56
+ 121, 121
+ 176, 189
+ 226, 231
+ 255, 253
+
+
+
+
+ 0, 3
+ 25, 19
+ 69, 57
+ 120, 123
+ 180, 197
+ 255, 253
+
+
+
+
+ 0, 0
+ 21, 10
+ 73, 56
+ 118, 120
+ 181, 198
+ 255, 253
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+
+
+ 8 8 8
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+
+
+ 100
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_29_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_29_formatted_canonical.xmp
new file mode 100644
index 0000000..3c3e489
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_29_formatted_canonical.xmp
@@ -0,0 +1,318 @@
+
+
+
+
+ 99
+ OLYMPUS DIGITAL CAMERA
+ 5
+ 189/100
+ 0/256
+ OLYMPUS M.14-42mm F3.5-5.6 II R
+ 14/1 42/1 35/10 56/10
+ ABGD50966
+ V5NG17123
+ True
+ 0
+ 134348800
+ -7
+ -12
+ -9
+ Adobe Standard
+ +4
+ 40
+ 50
+ 50
+ +11
+ False
+ 0.667102
+ 0.954481
+ 0
+ 0.03267
+ 0.903139
+ 0.09595
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +0.41
+ 0
+ +32
+ -8
+ True
+ True
+ -100
+ +100
+ -21
+ +33
+ +8
+ -8
+ -2
+ +8
+ -20
+ 0
+ 0
+ LensDefaults
+ -19
+ -16
+ +75
+ 0
+ -8
+ -33
+ +4
+ +68
+ 32
+ 72
+ 28
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ -9
+ 50
+ 0
+ 49
+ +1
+ 1
+ 6.7
+ +18
+ -27
+ +42
+ -100
+ -58
+ -10
+ -100
+ -32
+ +7
+ +1
+ -80
+ 0
+ +43
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4703
+ +22
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 1, 38
+ 55, 60
+ 118, 103
+ 189, 171
+ 255, 234
+
+
+
+
+ 0, 4
+ 22, 19
+ 66, 56
+ 121, 121
+ 176, 189
+ 226, 231
+ 255, 253
+
+
+
+
+ 0, 3
+ 25, 19
+ 69, 57
+ 120, 123
+ 180, 197
+ 255, 253
+
+
+
+
+ 0, 0
+ 21, 10
+ 73, 56
+ 118, 120
+ 181, 198
+ 255, 253
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 8.7.1
+ -64
+ 0
+ Custom
+ +41
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+ image/jpeg
+ 0
+ 0
+ 361471/100000
+ 2 0 2 0 0 1 1 2
+ 1
+ 0
+ 0
+ 2017-07-16T20:26:34
+ 100/100
+ 0230
+ 0/10
+ 1
+ 1
+ 1/250
+ 35/10
+ 3
+
+
+ False
+ False
+ 3
+ False
+ 0
+
+
+ 14/1
+ 28
+ 3
+ 87196351/32768
+ 87196351/32768
+ 0
+
+
+ 100
+
+
+ 0
+ 925/256
+ 5
+ 2920
+ 4045
+ 0
+ 0
+ 0
+ 7965784/1000000
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+ 0
+ 3
+ 2017-07-16T20:26:34
+ sRGB IEC61966-2.1
+ 6C18D5C06A7645432BE3ADFC93F4531B
+ 0
+
+
+ 8 8 8
+
+
+ 4045
+ 2920
+ OLYMPUS IMAGING CORP.
+ E-M10
+ 1
+ 2
+ 2
+ 3
+ 1500000/10000
+ 1500000/10000
+ 2017-07-16T20:26:34
+ Adobe Photoshop Lightroom 5.7.1 (Windows)
+ 2017-07-16T20:26:34
+ 2017-07-16T20:26:34
+ 5
+
+
+ 492DCFEBB48E251B850E7B51EAD4BA26
+ 492DCFEBB48E251B850E7B51EAD4BA26
+
+
+ xmp.did:5ea9d51e-d405-b74e-ab99-6a8feaf23437
+
+
+
+
+ derived
+ converted from image/x-olympus-raw to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:5ea9d51e-d405-b74e-ab99-6a8feaf23437
+ Adobe Photoshop Lightroom 5.7.1 (Windows)
+ 2017-07-17T23:42:33+03:00
+
+
+
+
+ saved
+ /
+ xmp.iid:8b895bf3-dc2a-ad46-87ec-b744c65e1868
+ Adobe Photoshop CC (Windows)
+ 2017-07-17T23:54:12+03:00
+
+
+
+
+ xmp.iid:8b895bf3-dc2a-ad46-87ec-b744c65e1868
+ 492DCFEBB48E251B850E7B51EAD4BA26
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_29_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_29_formatted_compact.xmp
new file mode 100644
index 0000000..ccba726
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_29_formatted_compact.xmp
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 1, 38
+ 55, 60
+ 118, 103
+ 189, 171
+ 255, 234
+
+
+
+
+ 0, 4
+ 22, 19
+ 66, 56
+ 121, 121
+ 176, 189
+ 226, 231
+ 255, 253
+
+
+
+
+ 0, 3
+ 25, 19
+ 69, 57
+ 120, 123
+ 180, 197
+ 255, 253
+
+
+
+
+ 0, 0
+ 21, 10
+ 73, 56
+ 118, 120
+ 181, 198
+ 255, 253
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+
+
+
+ 100
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+
+
+ 8 8 8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_2_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_2_formatted_canonical.xmp
new file mode 100644
index 0000000..90a1663
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_2_formatted_canonical.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+ 2022-09-27T13:49:21.000
+
+
+ Braut
+ Bräutigam
+ Paar
+ After-Party
+
+
+
+ 3
+ False
+ XF18-55mmF2.8-4 R LM OIS
+ 1337
+
+
+ Stefan Oltmann
+
+
+
+
+ John Doe
+
+
+ 370/100
+ 317/100
+ 1
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+ 32/10
+ 0
+ 2022-09-27T13:49:21
+ 0/100
+ 1
+ 1
+ 10/1000
+ 360/100
+ 3
+
+
+ False
+ False
+ 0
+ False
+ 0
+
+
+ 3580/100
+ 54
+ 3
+ 1882/1
+ 1882/1
+
+
+ 2000
+
+
+ 0
+ 300/100
+ 5
+ 4416
+ 2944
+ 0
+ 1
+ 2
+ 2
+ 667/100
+ 0
+
+
+
+
+
+ 0
+ 2022-09-27T13:49:21
+ FUJIFILM
+ X-T4
+ 1
+ 2
+ 72/1
+ 2
+ 72/1
+ 2022-09-27T13:49:21
+ Digital Camera X-T4 Ver1.40
+
+ 2022-09-27T13:49:21
+ 3
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_2_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_2_formatted_compact.xmp
new file mode 100644
index 0000000..31a639a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_2_formatted_compact.xmp
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+ Braut
+ Bräutigam
+ Paar
+ After-Party
+
+
+
+
+ Stefan Oltmann
+
+
+
+
+ John Doe
+
+
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+
+
+
+ 2000
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_3.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_3.xmp
new file mode 100644
index 0000000..3275dc1
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_3.xmp
@@ -0,0 +1,631 @@
+
+
+
+
+
+ True
+ 0/256
+ True
+ OLYMPUS M.30mm F3.5 Macro
+ 30/1 30/1 35/10 35/10
+ ACBA37456
+ BHXB09700
+
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+ image/jpeg
+
+
+
+ 2020-04-30T15:08:15
+
+
+
+ 2020-04-30T15:08:15
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2020-04-30T21:24:18+03:00
+ 2020-04-30T21:24:18+03:00
+ 2
+
+
+
+
+ 4086326A817940C408F83F2277090335
+ 4086326A817940C408F83F2277090335
+
+ xmp.did:ff982fc2-4308-7048-beec-991da4c7c819
+
+
+
+ derived
+ converted from image/x-olympus-raw to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:ff982fc2-4308-7048-beec-991da4c7c819
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2020-04-30T21:24:18+03:00
+
+
+
+ xmp.iid:ff982fc2-4308-7048-beec-991da4c7c819
+ 4086326A817940C408F83F2277090335
+
+
+
+ True
+ 1
+ 134348800
+ +10
+ -15
+ +25
+ Camera Natural
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+ 0.702472
+ +100
+ true
+ 0.345434
+ 1.000000
+ +50
+ 0.519469
+ 0
+ 0.448111
+ +2
+ Mask/CircularGradient
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.286750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ -16
+ 25
+ 50
+ 50
+ 0
+ False
+ -6.96018
+ 0.927253
+ 0
+ 0.16874
+ 0.683865
+ 0.255126
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.471141
+ 0.939926
+ 1.000000
+ Mask/Gradient
+ 0.447089
+ 0.655993
+
+
+
+ 0.000000
+ 0.000000
+ 0.007750
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.100750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -1.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ 0
+ -10
+ -10
+ True
+ True
+ -84
+ 0
+ +5
+ +18
+ +15
+ 0
+ +30
+ +8
+ -48
+ 0
+ 1
+ LensDefaults
+ +1
+ -10
+ -47
+ -25
+ -6
+ -10
+ -20
+ 0
+ 0
+ 50
+ 10
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+
+
+ d 0.610881 0.481687
+ d 0.620105 0.534419
+ d 0.625388 0.588143
+ d 0.625680 0.642374
+ d 0.625680 0.696612
+ d 0.629284 0.750435
+ d 0.633725 0.800150
+ d 0.642095 0.747157
+ d 0.645528 0.693143
+ d 0.647145 0.638948
+ d 0.647245 0.584723
+ d 0.644364 0.530632
+ d 0.647204 0.582074
+ d 0.650448 0.636140
+ d 0.653721 0.690203
+ d 0.655921 0.741574
+ d 0.653779 0.687442
+ d 0.646901 0.633994
+ d 0.637894 0.581127
+ d 0.628722 0.528295
+ d 0.625328 0.540444
+ d 0.624725 0.594676
+ d 0.625022 0.648914
+ d 0.626352 0.607175
+ d 0.623402 0.553079
+ d 0.616672 0.500606
+ d 0.616949 0.554842
+ d 0.616428 0.550300
+ d 0.607216 0.497549
+ d 0.596753 0.445147
+ d 0.600021 0.478894
+ d 0.598434 0.454855
+ d 0.581926 0.409236
+ d 0.590936 0.461899
+ d 0.604927 0.512212
+ d 0.610550 0.562039
+ d 0.620352 0.614649
+ d 0.626943 0.668152
+ d 0.631716 0.721999
+ d 0.641849 0.774397
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.242839 0.820612
+ d 0.237692 0.834207
+ d 0.237692 0.779969
+ d 0.237928 0.791930
+ d 0.240202 0.826093
+ d 0.241552 0.839256
+ d 0.240816 0.837668
+ d 0.231901 0.842783
+ d 0.230272 0.788652
+ d 0.224501 0.773922
+ d 0.224501 0.719683
+ d 0.225483 0.704995
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.226110 0.664020
+ d 0.232486 0.717588
+ d 0.232799 0.670566
+ d 0.233025 0.724795
+ d 0.234153 0.699187
+ d 0.231798 0.712948
+ d 0.221593 0.682138
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.217424 0.649862
+ d 0.223416 0.703495
+ d 0.231698 0.656464
+ d 0.233487 0.705027
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.236083 0.670884
+ d 0.239606 0.724917
+ d 0.238537 0.687193
+ d 0.236000 0.673476
+ d 0.235129 0.727702
+ d 0.236083 0.684962
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.229327 0.362420
+ d 0.224352 0.416251
+ d 0.239816 0.377775
+ d 0.236548 0.408153
+ d 0.237542 0.423037
+ d 0.244602 0.416914
+ d 0.249595 0.394578
+ d 0.242510 0.447946
+ d 0.238985 0.501936
+ d 0.240338 0.544066
+ d 0.238908 0.597662
+ d 0.238075 0.651889
+ d 0.237370 0.706067
+ d 0.231901 0.758224
+ d 0.231963 0.811572
+ d 0.237586 0.865236
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.640800 0.779855
+ d 0.645762 0.726080
+ d 0.648727 0.672068
+ d 0.646492 0.617994
+ d 0.639456 0.564592
+ d 0.631113 0.511529
+ d 0.618556 0.459987
+ d 0.609974 0.429956
+ d 0.621597 0.481932
+ d 0.633149 0.533923
+ d 0.628719 0.502499
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.622463 0.479542
+ d 0.623864 0.533748
+ d 0.627279 0.587781
+ d 0.624503 0.575236
+ d 0.618239 0.521646
+ d 0.616598 0.504911
+ d 0.623071 0.558458
+ d 0.626645 0.546009
+ d 0.624722 0.491993
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.614420 0.446507
+ d 0.625375 0.498742
+ d 0.636331 0.550977
+ d 0.628723 0.513678
+ d 0.616257 0.462099
+ d 0.599426 0.412752
+ d 0.574708 0.369997
+ d 0.590505 0.388526
+ d 0.567561 0.357011
+ d 0.544818 0.350241
+ d 0.569429 0.363618
+ d 0.537043 0.338195
+ d 0.571237 0.363120
+ d 0.588229 0.410874
+ d 0.602036 0.461893
+ d 0.607968 0.515512
+ d 0.610459 0.569562
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.611203 0.580790
+ d 0.613860 0.634912
+ d 0.619654 0.688584
+ d 0.628727 0.741443
+ d 0.640476 0.793359
+ d 0.655437 0.843586
+ d 0.680571 0.885792
+ d 0.714799 0.914002
+ d 0.737572 0.921030
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.069750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 60
+ 14
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ -38
+ 50
+ 0
+ 50
+ 0
+ 1
+ 6.7
+ +10
+ 0
+ +5
+ -16
+ -65
+ -30
+ -18
+ -28
+ -10
+ -19
+ -31
+ 0
+ +15
+ 25
+ 81
+ +1.2
+ 58
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4650
+ +11
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 63, 64
+ 125, 125
+ 192, 191
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 26
+ 119, 124
+ 181, 200
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 25
+ 119, 125
+ 182, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 25
+ 118, 124
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 10.1
+ 0
+ 0
+ As Shot
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_30.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_30.xmp
new file mode 100644
index 0000000..a5c8875
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_30.xmp
@@ -0,0 +1,273 @@
+
+
+
+
+
+ 4294967295/1
+ True
+ 1.1.6
+ 0/1
+ 0
+ EF24mm f/1.4L II USM
+ 250
+ 24/1 24/1 0/0 0/0
+ 00008011d2
+ 282028000176
+ True
+
+
+
+
+
+ Roberto_Nickson
+
+
+ image/jpeg
+
+
+
+ EF24mm f/1.4L II USM
+
+
+
+ 2019-03-24T21:41:32.40
+
+
+
+ 2019-03-24T21:41:32.40
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-03-26T12:39:50-07:00
+ 2019-03-26T12:39:50-07:00
+ 2
+
+
+
+
+ 19485E42BCF4B4ED15E4289BD270EA90
+ 19485E42BCF4B4ED15E4289BD270EA90
+
+ xmp.did:acf5ad62-5a32-41ec-a347-ac018863d49a
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:acf5ad62-5a32-41ec-a347-ac018863d49a
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-03-26T12:39:50-07:00
+
+
+
+ xmp.iid:acf5ad62-5a32-41ec-a347-ac018863d49a
+ 19485E42BCF4B4ED15E4289BD270EA90
+
+
+
+ True
+ 0
+ -14
+ -50
+ +10
+ Adobe Standard
+ 6665EF7EAB3B16A5E8CBA6338E37B35B
+ +15
+ 25
+ 50
+ 50
+ -29
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +2
+ +0.20
+ 0
+ 0
+ 0
+ False
+ True
+ -100
+ 0
+ 0
+ 0
+ 0
+ -5
+ 0
+ 0
+ -5
+ 0
+ 100
+ 068D7F4D2915DBE2A2F4798114B12B39
+ 100
+ 1
+ Canon EOS 5D Mark II (Canon EF 24mm f1.4L II USM) - RAW.lcp
+ Adobe (Canon EF 24mm f/1.4L II USM)
+ Custom
+ 27
+
+
+
+ 0
+ 0
+ 0
+ 0
+ -5
+ 0
+ 0
+ +3
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ AH6I4784.CR2
+ 0
+ 0
+ +3
+ 0
+ 0
+ 0
+ 0
+ +10
+ 0
+ 0
+ +10
+ 0
+ +25
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 234
+ 11
+ 211
+ 10
+ 7387
+ +19
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 30
+ 39, 42
+ 109, 109
+ 216, 221
+ 255, 240
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2
+ +10
+ 0
+ Custom
+ +39
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_30_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_30_formatted_canonical.xmp
new file mode 100644
index 0000000..bdee4ee
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_30_formatted_canonical.xmp
@@ -0,0 +1,238 @@
+
+
+
+
+ 4294967295/1
+ True
+ 1.1.6
+ 0/1
+ 0
+ EF24mm f/1.4L II USM
+ 250
+ 24/1 24/1 0/0 0/0
+ 00008011d2
+ 282028000176
+ True
+ True
+ 0
+ -14
+ -50
+ +10
+ Adobe Standard
+ 6665EF7EAB3B16A5E8CBA6338E37B35B
+ +15
+ 25
+ 50
+ 50
+ -29
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +2
+ +0.20
+ 0
+ 0
+ 0
+ False
+ True
+ -100
+ 0
+ 0
+ 0
+ 0
+ -5
+ 0
+ 0
+ -5
+ 0
+ 100
+ 068D7F4D2915DBE2A2F4798114B12B39
+ 100
+ 1
+ Canon EOS 5D Mark II (Canon EF 24mm f1.4L II USM) - RAW.lcp
+ Adobe (Canon EF 24mm f/1.4L II USM)
+ Custom
+ 27
+
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ -5
+ 0
+ 0
+ +3
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ AH6I4784.CR2
+ 0
+ 0
+ +3
+ 0
+ 0
+ 0
+ 0
+ +10
+ 0
+ 0
+ +10
+ 0
+ +25
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 234
+ 11
+ 211
+ 10
+ 7387
+ +19
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 30
+ 39, 42
+ 109, 109
+ 216, 221
+ 255, 240
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2
+ +10
+ 0
+ Custom
+ +39
+
+
+ Roberto_Nickson
+
+
+ image/jpeg
+ EF24mm f/1.4L II USM
+ 2019-03-24T21:41:32.40
+ 2019-03-24T21:41:32.40
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-03-26T12:39:50-07:00
+ 2019-03-26T12:39:50-07:00
+ 2
+
+
+ 19485E42BCF4B4ED15E4289BD270EA90
+ 19485E42BCF4B4ED15E4289BD270EA90
+
+
+ xmp.did:acf5ad62-5a32-41ec-a347-ac018863d49a
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:acf5ad62-5a32-41ec-a347-ac018863d49a
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-03-26T12:39:50-07:00
+
+
+
+
+ xmp.iid:acf5ad62-5a32-41ec-a347-ac018863d49a
+ 19485E42BCF4B4ED15E4289BD270EA90
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_30_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_30_formatted_compact.xmp
new file mode 100644
index 0000000..470c488
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_30_formatted_compact.xmp
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 30
+ 39, 42
+ 109, 109
+ 216, 221
+ 255, 240
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ Roberto_Nickson
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_31.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_31.xmp
new file mode 100644
index 0000000..2b09f15
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_31.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_31_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_31_formatted_canonical.xmp
new file mode 100644
index 0000000..63bda32
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_31_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_31_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_31_formatted_compact.xmp
new file mode 100644
index 0000000..7fec24e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_31_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_32.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_32.xmp
new file mode 100644
index 0000000..f36a6f7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_32.xmp
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+ CEDRIC LETSCH
+
+
+
+
+
+ 2019-12-15T16:49:46+01:00
+ 2
+
+
+
+ xmp.did:b8001bea-63e8-2840-8a34-19db36af1eee
+ xmp.iid:b8001bea-63e8-2840-8a34-19db36af1eee
+ 9449E7C197C7E6BB1B083E540FA60792
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_32_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_32_formatted_canonical.xmp
new file mode 100644
index 0000000..446e3c4
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_32_formatted_canonical.xmp
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ CEDRIC LETSCH
+
+
+ 2019-12-15T16:49:46+01:00
+ 2
+ xmp.did:b8001bea-63e8-2840-8a34-19db36af1eee
+ xmp.iid:b8001bea-63e8-2840-8a34-19db36af1eee
+ 9449E7C197C7E6BB1B083E540FA60792
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_32_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_32_formatted_compact.xmp
new file mode 100644
index 0000000..f99dbcd
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_32_formatted_compact.xmp
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ CEDRIC LETSCH
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_33.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_33.xmp
new file mode 100644
index 0000000..a676055
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_33.xmp
@@ -0,0 +1,236 @@
+
+
+
+
+
+ E 55-210mm F4.5-6.3 OSS
+ 550/10 2100/10 45/10 63/10
+
+
+
+ image/jpeg
+
+
+
+ 2017-11-09T02:25:39
+
+
+
+ 2017-11-09T02:25:39
+ Adobe Photoshop Lightroom Classic 7.0 (Macintosh)
+ 2017-11-09T18:34:46-07:00
+ 2017-11-09T18:34:46-07:00
+ 1
+
+
+
+
+ FD18C8BF658F54965D8EED98B6708202
+ FD18C8BF658F54965D8EED98B6708202
+
+ xmp.did:007e74df-3626-4d22-94b8-eb35593d5ef3
+
+
+
+ derived
+ saved to new location
+
+
+ saved
+ /
+ xmp.iid:007e74df-3626-4d22-94b8-eb35593d5ef3
+ Adobe Photoshop Lightroom Classic 7.0 (Macintosh)
+ 2017-11-09T18:34:46-07:00
+
+
+
+ xmp.iid:007e74df-3626-4d22-94b8-eb35593d5ef3
+ FD18C8BF658F54965D8EED98B6708202
+
+
+
+ True
+ 0
+ -9
+ -70
+ -100
+ Embedded
+ 0
+ 0
+ +45
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.55
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ +18
+ -18
+ 0
+ 0
+ +57
+ 0
+ +58
+ +71
+ -28
+ -19
+ 0
+ 0
+ LensDefaults
+ -68
+ -84
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 10.0
+ 0
+ 0
+ 0
+ +41
+ -5
+ -82
+ 0
+ 0
+ 0
+ 0
+ 0
+ +85
+ -38
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 10.0
+ 0
+ 0
+ Custom
+ +38
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_33_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_33_formatted_canonical.xmp
new file mode 100644
index 0000000..190060d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_33_formatted_canonical.xmp
@@ -0,0 +1,202 @@
+
+
+
+
+ E 55-210mm F4.5-6.3 OSS
+ 550/10 2100/10 45/10 63/10
+ True
+ 0
+ -9
+ -70
+ -100
+ Embedded
+ 0
+ 0
+ +45
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.55
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ +18
+ -18
+ 0
+ 0
+ +57
+ 0
+ +58
+ +71
+ -28
+ -19
+ 0
+ 0
+ LensDefaults
+ -68
+ -84
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 10.0
+ 0
+ 0
+ 0
+ +41
+ -5
+ -82
+ 0
+ 0
+ 0
+ 0
+ 0
+ +85
+ -38
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 10.0
+ 0
+ 0
+ Custom
+ +38
+ image/jpeg
+ 2017-11-09T02:25:39
+ 2017-11-09T02:25:39
+ Adobe Photoshop Lightroom Classic 7.0 (Macintosh)
+ 2017-11-09T18:34:46-07:00
+ 2017-11-09T18:34:46-07:00
+ 1
+
+
+ FD18C8BF658F54965D8EED98B6708202
+ FD18C8BF658F54965D8EED98B6708202
+
+
+ xmp.did:007e74df-3626-4d22-94b8-eb35593d5ef3
+
+
+
+
+ derived
+ saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:007e74df-3626-4d22-94b8-eb35593d5ef3
+ Adobe Photoshop Lightroom Classic 7.0 (Macintosh)
+ 2017-11-09T18:34:46-07:00
+
+
+
+
+ xmp.iid:007e74df-3626-4d22-94b8-eb35593d5ef3
+ FD18C8BF658F54965D8EED98B6708202
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_33_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_33_formatted_compact.xmp
new file mode 100644
index 0000000..f47d317
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_33_formatted_compact.xmp
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_34.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_34.xmp
new file mode 100644
index 0000000..2b09f15
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_34.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_34_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_34_formatted_canonical.xmp
new file mode 100644
index 0000000..63bda32
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_34_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_34_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_34_formatted_compact.xmp
new file mode 100644
index 0000000..7fec24e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_34_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_35.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_35.xmp
new file mode 100644
index 0000000..5fcabee
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_35.xmp
@@ -0,0 +1,291 @@
+
+
+
+
+
+ 63/100
+ True
+ 1.0.2
+ 0/1
+ 0
+ True
+ EF100mm f/2.8L Macro IS USM
+ 254
+ 100/1 100/1 0/0 0/0
+ 000002e702
+ 043052000677
+ True
+
+
+
+ image/jpeg
+
+
+
+ EF100mm f/2.8L Macro IS USM
+
+
+
+ 2018-12-12T17:11:15.89
+
+
+
+ 2018-12-12T17:11:15.89
+ Adobe Photoshop Lightroom Classic 8.1 (Windows)
+ 2019-01-12T20:37:50+01:00
+ 2019-01-12T20:37:50+01:00
+ 5
+
+
+
+
+ A9EAC33A4A2953845B32B61730EBBD8F
+ xmp.iid:2e072152-2b28-49b6-a8df-b7704e92f189
+ A9EAC33A4A2953845B32B61730EBBD8F
+
+ xmp.did:81300372-d116-564c-b59e-b8f83ae6fd1c
+
+
+
+ saved
+ /metadata
+ xmp.iid:cf4a2997-e668-4776-a381-6d5d39127075
+ Adobe Photoshop Camera Raw 11.0
+ 2018-12-12T20:13:07-06:00
+
+
+ saved
+ /metadata
+ xmp.iid:2e072152-2b28-49b6-a8df-b7704e92f189
+ Adobe Photoshop Camera Raw 11.0 (Macintosh)
+ 2018-12-12T20:13:07-06:00
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:81300372-d116-564c-b59e-b8f83ae6fd1c
+ Adobe Photoshop Lightroom Classic 8.1 (Windows)
+ 2019-01-12T20:37:50+01:00
+
+
+
+ xmp.iid:81300372-d116-564c-b59e-b8f83ae6fd1c
+ A9EAC33A4A2953845B32B61730EBBD8F
+
+
+
+ True
+ 1
+ -30
+ 0
+ 0
+ Adobe Standard
+ 661433344C8532AFA5A1E9091401E43C
+ +13
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 0.944778
+ 0
+ 0.1352
+ 0.6384
+ 0.348139
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.56
+ 0
+ 0
+ 0
+ True
+ True
+ -52
+ +8
+ +14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ D67CEB30869BB47B1118D1BF99FA4AA5
+ 100
+ 1
+ Canon EOS 5D Mark II (Canon EF 100mm f2.8 L Macro IS USM) - RAW.lcp
+ Adobe (Canon EF 100mm f2.8 L Macro IS USM)
+ LensDefaults
+ 100
+
+
+
+ 0
+ 0
+ -14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 14
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _V7A8887.CR2
+ 0
+ 0
+ +20
+ +4
+ +3
+ +14
+ 0
+ 0
+ 0
+ 0
+ -30
+ 0
+ +56
+ 25
+ 51
+ +1.0
+ 46
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5500
+ +10
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 78, 66
+ 199, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.1
+ +28
+ 0
+ Daylight
+ -1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_35_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_35_formatted_canonical.xmp
new file mode 100644
index 0000000..793dd52
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_35_formatted_canonical.xmp
@@ -0,0 +1,260 @@
+
+
+
+
+ 63/100
+ True
+ 1.0.2
+ 0/1
+ 0
+ True
+ EF100mm f/2.8L Macro IS USM
+ 254
+ 100/1 100/1 0/0 0/0
+ 000002e702
+ 043052000677
+ True
+ True
+ 1
+ -30
+ 0
+ 0
+ Adobe Standard
+ 661433344C8532AFA5A1E9091401E43C
+ +13
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 0.944778
+ 0
+ 0.1352
+ 0.6384
+ 0.348139
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.56
+ 0
+ 0
+ 0
+ True
+ True
+ -52
+ +8
+ +14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ D67CEB30869BB47B1118D1BF99FA4AA5
+ 100
+ 1
+ Canon EOS 5D Mark II (Canon EF 100mm f2.8 L Macro IS USM) - RAW.lcp
+ Adobe (Canon EF 100mm f2.8 L Macro IS USM)
+ LensDefaults
+ 100
+
+
+
+
+
+ 0
+ 0
+ -14
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 14
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _V7A8887.CR2
+ 0
+ 0
+ +20
+ +4
+ +3
+ +14
+ 0
+ 0
+ 0
+ 0
+ -30
+ 0
+ +56
+ 25
+ 51
+ +1.0
+ 46
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5500
+ +10
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 78, 66
+ 199, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.1
+ +28
+ 0
+ Daylight
+ -1
+ image/jpeg
+ EF100mm f/2.8L Macro IS USM
+ 2018-12-12T17:11:15.89
+ 2018-12-12T17:11:15.89
+ Adobe Photoshop Lightroom Classic 8.1 (Windows)
+ 2019-01-12T20:37:50+01:00
+ 2019-01-12T20:37:50+01:00
+ 5
+
+
+ A9EAC33A4A2953845B32B61730EBBD8F
+ xmp.iid:2e072152-2b28-49b6-a8df-b7704e92f189
+ A9EAC33A4A2953845B32B61730EBBD8F
+
+
+ xmp.did:81300372-d116-564c-b59e-b8f83ae6fd1c
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:cf4a2997-e668-4776-a381-6d5d39127075
+ Adobe Photoshop Camera Raw 11.0
+ 2018-12-12T20:13:07-06:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:2e072152-2b28-49b6-a8df-b7704e92f189
+ Adobe Photoshop Camera Raw 11.0 (Macintosh)
+ 2018-12-12T20:13:07-06:00
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:81300372-d116-564c-b59e-b8f83ae6fd1c
+ Adobe Photoshop Lightroom Classic 8.1 (Windows)
+ 2019-01-12T20:37:50+01:00
+
+
+
+
+ xmp.iid:81300372-d116-564c-b59e-b8f83ae6fd1c
+ A9EAC33A4A2953845B32B61730EBBD8F
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_35_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_35_formatted_compact.xmp
new file mode 100644
index 0000000..1d43347
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_35_formatted_compact.xmp
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 78, 66
+ 199, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_36.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_36.xmp
new file mode 100644
index 0000000..f0876e3
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_36.xmp
@@ -0,0 +1,63 @@
+
+
+
+
+
+ XF90mmF2 R LM WR
+ 9000/100 9000/100 200/100 200/100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2017-06-26T23:18:45
+
+
+
+ 2017:06:26 23:18:45
+ Digital Camera X-Pro2 Ver3.00
+ 2017:06:26 23:18:45
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_36_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_36_formatted_canonical.xmp
new file mode 100644
index 0000000..05d0a7f
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_36_formatted_canonical.xmp
@@ -0,0 +1,29 @@
+
+
+
+
+ XF90mmF2 R LM WR
+ 9000/100 9000/100 200/100 200/100
+
+
+
+
+
+
+
+
+
+
+ 2017-06-26T23:18:45
+ 2017:06:26 23:18:45
+ Digital Camera X-Pro2 Ver3.00
+ 2017:06:26 23:18:45
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_36_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_36_formatted_compact.xmp
new file mode 100644
index 0000000..1539480
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_36_formatted_compact.xmp
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_37.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_37.xmp
new file mode 100644
index 0000000..927ed7f
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_37.xmp
@@ -0,0 +1,832 @@
+
+
+
+
+
+ 4294967295/1
+ True
+ 51903
+ True
+ 35.0 mm f/1.8
+ 159
+ 350/10 350/10 18/10 18/10
+ 4305691
+ True
+
+
+
+ image/jpeg
+
+
+
+ 3
+ 2018-07-27T22:02:03.035
+
+
+ xmp.did:080e0744-eb2c-4fc5-af82-ce438fccb4ab
+ xmp.did:27d0c57c-ea4d-4548-90bc-8dd04bd88f5b
+ xmp.did:427b2bc9-7f5c-47c3-ad92-4f755a696fd4
+ xmp.did:445a0595-2032-4b56-a3aa-3390307f9fbd
+ xmp.did:590d76e7-9ab0-4d3d-b2ec-16ae48d63830
+ xmp.did:6a21a9af-e942-457a-920b-61413cbc9092
+ xmp.did:8948b37d-b6fc-4762-aab9-0aadc0978396
+ xmp.did:bd664069-5d0b-4f13-b44e-f70dd7f2d062
+ xmp.did:c9b36e10-f5af-469b-bbcc-6ff7ea85c522
+ xmp.did:eded9ce4-cfb1-42d8-a847-9c6e00b88dd6
+
+
+ Adobe RGB (1998)
+
+
+
+ 2018-07-27T22:02:03
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T11:31:57+02:00
+ 2018-07-30T11:31:57+02:00
+ 3
+
+
+
+
+ adobe:docid:photoshop:4741d4bf-06a2-8642-9e51-58107ae860c0
+ xmp.iid:3716d185-986d-4ce2-a7d7-e4a84227a3b8
+ CBE9E97CB36CBB122E8393CCAFA008AF
+
+ adobe:docid:photoshop:60075677-3d34-8641-affe-8bb51d30fed7
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/vnd.adobe.photoshop, saved to new location
+
+
+ saved
+ /
+ xmp.iid:6a21a9af-e942-457a-920b-61413cbc9092
+ Adobe Photoshop Lightroom 6.14 (Macintosh)
+ 2018-07-30T09:15:52+02:00
+
+
+ converted
+ from image/vnd.adobe.photoshop to application/vnd.adobe.photoshop
+
+
+ saved
+ /
+ xmp.iid:a88ad3df-b424-4259-a1a7-3cdbfc34de62
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T10:10:44+02:00
+
+
+ saved
+ /
+ xmp.iid:a7fb9e28-b67d-4cea-b92e-9e506137de16
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T10:58:42+02:00
+
+
+ converted
+ from application/vnd.adobe.photoshop to image/tiff
+
+
+ derived
+ converted from application/vnd.adobe.photoshop to image/tiff
+
+
+ saved
+ /
+ xmp.iid:a2c2087b-0725-4e76-8d47-6763b96e0dca
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T10:58:42+02:00
+
+
+ saved
+ /
+ xmp.iid:3716d185-986d-4ce2-a7d7-e4a84227a3b8
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T11:31:57+02:00
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:bda6841d-bc0c-4bb3-9d06-f4fe4906a6fe
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T11:31:57+02:00
+
+
+
+ xmp.iid:bda6841d-bc0c-4bb3-9d06-f4fe4906a6fe
+ CBE9E97CB36CBB122E8393CCAFA008AF
+
+
+
+ True
+ 1
+ 134348800
+ +29
+ 0
+ 0
+ Adobe Standard
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+ 0.757071
+ +100
+ true
+ 0.887510
+ 1.000000
+ +50
+ 0.964159
+ 0
+ 0.642098
+ +2
+ Mask/CircularGradient
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.243215
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -1.000000
+ 0.470035
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+ 0.763553
+ +100
+ true
+ 0.042814
+ 1.000000
+ +50
+ 0.119516
+ 0
+ 0.648501
+ +2
+ Mask/CircularGradient
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.243215
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -1.000000
+ 0.470035
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ +17
+ 25
+ 50
+ 50
+ -12
+ False
+ 0
+ 0.999036
+ 0
+ 0.020595
+ 0.999008
+ 0.020623
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.70
+ 0
+ 0
+ 0
+ True
+ True
+ -69
+ +100
+ 0
+ -20
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ 828DA2C4D03813F778CDB4DA7DA6EC3D
+ 100
+ 1
+ NIKON D90 (Nikon AF-S DX NIKKOR 35mm f1.8G) - RAW.lcp
+ Adobe (Nikon AF-S DX NIKKOR 35mm f/1.8G)
+ LensDefaults
+ 100
+ 0
+ -100
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 5
+ 0
+ 0.00
+ 0.00
+ -16
+ 50
+ 0
+ 50
+ 0
+ 1
+ 6.7
+ 0
+ 0
+
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.007729
+ 0.007729
+ Mask/Ellipse
+ 0.952933
+ 0.326332
+
+
+
+ gaussian
+ 0.352332
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.964600
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.500000
+
+
+ d 0.849125 0.347288
+ r 0.007826
+ d 0.851469 0.347475
+ r 0.007824
+ d 0.853814 0.347662
+ r 0.007823
+ d 0.855413 0.347786
+ r 0.007821
+ d 0.857756 0.347969
+ r 0.007818
+ d 0.860099 0.348151
+ d 0.860287 0.348178
+ r 0.007816
+ d 0.862621 0.348511
+ r 0.007814
+ d 0.864955 0.348845
+ r 0.007812
+ d 0.867289 0.349178
+ r 0.007811
+ d 0.868758 0.349533
+ r 0.007809
+ d 0.871070 0.350092
+ r 0.007807
+ d 0.873383 0.350651
+ r 0.007806
+ d 0.874200 0.350826
+ r 0.007804
+ d 0.876518 0.351321
+ r 0.007802
+ d 0.878836 0.351817
+ r 0.007801
+ d 0.880464 0.352185
+ r 0.007799
+ d 0.882237 0.352481
+ r 0.007798
+ d 0.882948 0.352617
+ r 0.007796
+ d 0.885268 0.353059
+ r 0.007795
+ d 0.887588 0.353501
+ d 0.887632 0.353508
+ r 0.007793
+ d 0.889956 0.353888
+ r 0.007792
+ d 0.890735 0.353980
+ r 0.007790
+ d 0.893065 0.354255
+ r 0.007788
+ d 0.895394 0.354530
+ r 0.007787
+ d 0.896062 0.354554
+ r 0.007785
+ d 0.898398 0.354637
+ r 0.007783
+ d 0.900177 0.354754
+ r 0.007781
+ d 0.902509 0.354908
+ r 0.007779
+ d 0.904741 0.355546
+ r 0.007777
+ d 0.907032 0.356201
+ r 0.007775
+ d 0.909324 0.356856
+ r 0.007773
+ d 0.911615 0.357511
+ r 0.007771
+ d 0.913906 0.358166
+ d 0.914001 0.358195
+ r 0.007769
+ d 0.916286 0.358882
+ r 0.007767
+ d 0.918571 0.359570
+ r 0.007765
+ d 0.920855 0.360257
+ r 0.007764
+ d 0.922181 0.360557
+ r 0.007762
+ d 0.924483 0.361079
+ r 0.007759
+ d 0.926785 0.361600
+ r 0.007757
+ d 0.929087 0.362121
+ r 0.007756
+ d 0.929717 0.362249
+ r 0.007754
+ d 0.932022 0.362718
+ r 0.007753
+ d 0.933421 0.363132
+ r 0.007752
+ d 0.934440 0.363353
+ r 0.007750
+ d 0.936742 0.363852
+ r 0.007748
+ d 0.939043 0.364351
+ r 0.007746
+ d 0.941352 0.364726
+ r 0.007745
+ d 0.942369 0.365003
+ r 0.007743
+ d 0.944655 0.365626
+ r 0.007742
+ d 0.945177 0.365761
+ r 0.007740
+ d 0.947465 0.366354
+ r 0.007738
+ d 0.949753 0.366948
+ r 0.007737
+ d 0.950559 0.367149
+ r 0.007735
+ d 0.952848 0.367720
+ r 0.007733
+ d 0.955137 0.368291
+ r 0.007731
+ d 0.957339 0.368914
+ r 0.007729
+ d 0.958665 0.369120
+ r 0.007728
+ d 0.960478 0.369472
+ d 0.960751 0.369534
+
+
+ 1.000000
+ 1.000000
+ 0.007828
+ Mask/Paint
+
+
+
+ gaussian
+ 0.374538
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.850625
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006717
+ 0.006717
+ Mask/Ellipse
+ 0.435560
+ 0.429980
+
+
+
+ gaussian
+ 0.417480
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.463893
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006698
+ 0.006698
+ Mask/Ellipse
+ 0.421605
+ 0.311260
+
+
+
+ gaussian
+ 0.322260
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.443605
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.500000
+
+
+ d 0.404457 0.347800
+ d 0.405823 0.345588
+ d 0.407189 0.343376
+ d 0.408555 0.341163
+ d 0.409103 0.340276
+
+
+ 1.000000
+ 1.000000
+ 0.006701
+ Mask/Paint
+
+
+
+ gaussian
+ 0.362800
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.416624
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006684
+ 0.006684
+ Mask/Ellipse
+ 0.382711
+ 0.286331
+
+
+
+ gaussian
+ 0.314831
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.332044
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006681
+ 0.006681
+ Mask/Ellipse
+ 0.509684
+ 0.212355
+
+
+
+ gaussian
+ 0.212355
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.472017
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006650
+ 0.006650
+ Mask/Ellipse
+ 0.638123
+ 0.181222
+
+
+
+ gaussian
+ 0.181222
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.660456
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006516
+ 0.006516
+ Mask/Ellipse
+ 0.796742
+ 0.013264
+
+
+
+ gaussian
+ 0.027264
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.781075
+ heal
+
+
+ 0.388672
+ +2
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006407
+ 0.006407
+ Mask/Ellipse
+ 0.019471
+ 0.041553
+
+
+
+ gaussian
+ 0.041553
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.036304
+ heal
+
+
+
+
+
+ centerX = 0.952933, centerY = 0.326332, radius = 0.007729, sourceState = sourceAutoComputed, sourceX = 0.964600, sourceY = 0.352332, spotType = heal
+ centerX = 0.435560, centerY = 0.429980, radius = 0.006717, sourceState = sourceAutoComputed, sourceX = 0.463893, sourceY = 0.417480, spotType = heal
+ centerX = 0.421605, centerY = 0.311260, radius = 0.006698, sourceState = sourceAutoComputed, sourceX = 0.443605, sourceY = 0.322260, spotType = heal
+ centerX = 0.382711, centerY = 0.286331, radius = 0.006684, sourceState = sourceAutoComputed, sourceX = 0.332044, sourceY = 0.314831, spotType = heal
+ centerX = 0.509684, centerY = 0.212355, radius = 0.006681, sourceState = sourceAutoComputed, sourceX = 0.472017, sourceY = 0.212355, spotType = heal
+ centerX = 0.638123, centerY = 0.181222, radius = 0.006650, sourceState = sourceAutoComputed, sourceX = 0.660456, sourceY = 0.181222, spotType = heal
+ centerX = 0.796742, centerY = 0.013264, radius = 0.006516, sourceState = sourceAutoComputed, sourceX = 0.781075, sourceY = 0.027264, spotType = heal
+ centerX = 0.019471, centerY = 0.041553, radius = 0.006407, sourceState = sourceAutoComputed, sourceX = 0.036304, sourceY = 0.041553, spotType = heal
+
+
+ 0
+ 0
+ +18
+ -42
+ 0
+ 0
+ 0
+ 0
+ -78
+ 0
+ -24
+ 32
+ 3
+ +0.6
+ 74
+ 0
+ 0
+ 3
+ 0
+ 2
+ 5750
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 761B2C1BEF1BA890ADA86F4EDEEDCC20
+ 53.5666
+ 0
+ 2
+ 0.811756074,0.416356998,0.820405652,0.758753367
+ 0.220619541,0.425669669,0.214266629,0.755766117
+ 761B2C1BEF1BA890ADA86F4EDEEDCC20
+ False
+ 6
+ 1.000000000,0.000000000,0.000000000,0.000000000,1.000000000,0.000000000,0.000000000,0.000000000,1.000000000
+ 1.066300504,0.025984937,-0.025539485,0.010894824,1.088947749,-0.021391182,0.017185023,0.049733695,1.000000000
+ 1.395543247,0.059107101,-0.059107101,0.253149986,1.232556603,-0.162274375,0.290117842,0.101836526,1.000000000
+ 1.001976674,0.000877864,-0.001427269,-0.001975194,1.001976673,-0.000000739,0.000000000,0.000000000,1.000000000
+ 1.085848322,0.046112967,-0.044602696,-0.004298181,1.151298389,-0.028010808,0.000000882,0.088461868,1.000000000
+ 1.083993316,0.041296964,-0.040345298,0.000000000,1.147595490,-0.029382324,0.000000000,0.086295285,1.000000000
+ 151388160
+ 10.1
+ +44
+ 0
+ Auto
+ +44
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_37_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_37_formatted_canonical.xmp
new file mode 100644
index 0000000..a121ec3
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_37_formatted_canonical.xmp
@@ -0,0 +1,866 @@
+
+
+
+
+ 4294967295/1
+ True
+ 51903
+ True
+ 35.0 mm f/1.8
+ 159
+ 350/10 350/10 18/10 18/10
+ 4305691
+ True
+ True
+ 1
+ 134348800
+ +29
+ 0
+ 0
+ Adobe Standard
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+ 0.757071
+ +100
+ true
+ 0.887510
+ 1.000000
+ +50
+ 0.964159
+ 0
+ 0.642098
+ +2
+ Mask/CircularGradient
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.243215
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -1.000000
+ 0.470035
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+ 0.763553
+ +100
+ true
+ 0.042814
+ 1.000000
+ +50
+ 0.119516
+ 0
+ 0.648501
+ +2
+ Mask/CircularGradient
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.243215
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -1.000000
+ 0.470035
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ +17
+ 25
+ 50
+ 50
+ -12
+ False
+ 0
+ 0.999036
+ 0
+ 0.020595
+ 0.999008
+ 0.020623
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.70
+ 0
+ 0
+ 0
+ True
+ True
+ -69
+ +100
+ 0
+ -20
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ 828DA2C4D03813F778CDB4DA7DA6EC3D
+ 100
+ 1
+ NIKON D90 (Nikon AF-S DX NIKKOR 35mm f1.8G) - RAW.lcp
+ Adobe (Nikon AF-S DX NIKKOR 35mm f/1.8G)
+ LensDefaults
+ 100
+ 0
+ -100
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 5
+ 0
+ 0.00
+ 0.00
+ -16
+ 50
+ 0
+ 50
+ 0
+ 1
+ 6.7
+ 0
+ 0
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.007729
+ 0.007729
+ Mask/Ellipse
+ 0.952933
+ 0.326332
+
+
+
+
+ gaussian
+ 0.352332
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.964600
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.500000
+
+
+ d 0.849125 0.347288
+ r 0.007826
+ d 0.851469 0.347475
+ r 0.007824
+ d 0.853814 0.347662
+ r 0.007823
+ d 0.855413 0.347786
+ r 0.007821
+ d 0.857756 0.347969
+ r 0.007818
+ d 0.860099 0.348151
+ d 0.860287 0.348178
+ r 0.007816
+ d 0.862621 0.348511
+ r 0.007814
+ d 0.864955 0.348845
+ r 0.007812
+ d 0.867289 0.349178
+ r 0.007811
+ d 0.868758 0.349533
+ r 0.007809
+ d 0.871070 0.350092
+ r 0.007807
+ d 0.873383 0.350651
+ r 0.007806
+ d 0.874200 0.350826
+ r 0.007804
+ d 0.876518 0.351321
+ r 0.007802
+ d 0.878836 0.351817
+ r 0.007801
+ d 0.880464 0.352185
+ r 0.007799
+ d 0.882237 0.352481
+ r 0.007798
+ d 0.882948 0.352617
+ r 0.007796
+ d 0.885268 0.353059
+ r 0.007795
+ d 0.887588 0.353501
+ d 0.887632 0.353508
+ r 0.007793
+ d 0.889956 0.353888
+ r 0.007792
+ d 0.890735 0.353980
+ r 0.007790
+ d 0.893065 0.354255
+ r 0.007788
+ d 0.895394 0.354530
+ r 0.007787
+ d 0.896062 0.354554
+ r 0.007785
+ d 0.898398 0.354637
+ r 0.007783
+ d 0.900177 0.354754
+ r 0.007781
+ d 0.902509 0.354908
+ r 0.007779
+ d 0.904741 0.355546
+ r 0.007777
+ d 0.907032 0.356201
+ r 0.007775
+ d 0.909324 0.356856
+ r 0.007773
+ d 0.911615 0.357511
+ r 0.007771
+ d 0.913906 0.358166
+ d 0.914001 0.358195
+ r 0.007769
+ d 0.916286 0.358882
+ r 0.007767
+ d 0.918571 0.359570
+ r 0.007765
+ d 0.920855 0.360257
+ r 0.007764
+ d 0.922181 0.360557
+ r 0.007762
+ d 0.924483 0.361079
+ r 0.007759
+ d 0.926785 0.361600
+ r 0.007757
+ d 0.929087 0.362121
+ r 0.007756
+ d 0.929717 0.362249
+ r 0.007754
+ d 0.932022 0.362718
+ r 0.007753
+ d 0.933421 0.363132
+ r 0.007752
+ d 0.934440 0.363353
+ r 0.007750
+ d 0.936742 0.363852
+ r 0.007748
+ d 0.939043 0.364351
+ r 0.007746
+ d 0.941352 0.364726
+ r 0.007745
+ d 0.942369 0.365003
+ r 0.007743
+ d 0.944655 0.365626
+ r 0.007742
+ d 0.945177 0.365761
+ r 0.007740
+ d 0.947465 0.366354
+ r 0.007738
+ d 0.949753 0.366948
+ r 0.007737
+ d 0.950559 0.367149
+ r 0.007735
+ d 0.952848 0.367720
+ r 0.007733
+ d 0.955137 0.368291
+ r 0.007731
+ d 0.957339 0.368914
+ r 0.007729
+ d 0.958665 0.369120
+ r 0.007728
+ d 0.960478 0.369472
+ d 0.960751 0.369534
+
+
+ 1.000000
+ 1.000000
+ 0.007828
+ Mask/Paint
+
+
+
+
+ gaussian
+ 0.374538
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.850625
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006717
+ 0.006717
+ Mask/Ellipse
+ 0.435560
+ 0.429980
+
+
+
+
+ gaussian
+ 0.417480
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.463893
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006698
+ 0.006698
+ Mask/Ellipse
+ 0.421605
+ 0.311260
+
+
+
+
+ gaussian
+ 0.322260
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.443605
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.500000
+
+
+ d 0.404457 0.347800
+ d 0.405823 0.345588
+ d 0.407189 0.343376
+ d 0.408555 0.341163
+ d 0.409103 0.340276
+
+
+ 1.000000
+ 1.000000
+ 0.006701
+ Mask/Paint
+
+
+
+
+ gaussian
+ 0.362800
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.416624
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006684
+ 0.006684
+ Mask/Ellipse
+ 0.382711
+ 0.286331
+
+
+
+
+ gaussian
+ 0.314831
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.332044
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006681
+ 0.006681
+ Mask/Ellipse
+ 0.509684
+ 0.212355
+
+
+
+
+ gaussian
+ 0.212355
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.472017
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006650
+ 0.006650
+ Mask/Ellipse
+ 0.638123
+ 0.181222
+
+
+
+
+ gaussian
+ 0.181222
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.660456
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006516
+ 0.006516
+ Mask/Ellipse
+ 0.796742
+ 0.013264
+
+
+
+
+ gaussian
+ 0.027264
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.781075
+ heal
+
+
+
+
+ 0.388672
+ +2
+
+
+
+
+ 0.000000
+ 1.000000
+ 1.000000
+ 0.000000
+ 0.006407
+ 0.006407
+ Mask/Ellipse
+ 0.019471
+ 0.041553
+
+
+
+
+ gaussian
+ 0.041553
+ 1.000000
+ +2
+ sourceAutoComputed
+ 0.036304
+ heal
+
+
+
+
+
+
+ centerX = 0.952933, centerY = 0.326332, radius = 0.007729, sourceState = sourceAutoComputed, sourceX = 0.964600, sourceY = 0.352332, spotType = heal
+ centerX = 0.435560, centerY = 0.429980, radius = 0.006717, sourceState = sourceAutoComputed, sourceX = 0.463893, sourceY = 0.417480, spotType = heal
+ centerX = 0.421605, centerY = 0.311260, radius = 0.006698, sourceState = sourceAutoComputed, sourceX = 0.443605, sourceY = 0.322260, spotType = heal
+ centerX = 0.382711, centerY = 0.286331, radius = 0.006684, sourceState = sourceAutoComputed, sourceX = 0.332044, sourceY = 0.314831, spotType = heal
+ centerX = 0.509684, centerY = 0.212355, radius = 0.006681, sourceState = sourceAutoComputed, sourceX = 0.472017, sourceY = 0.212355, spotType = heal
+ centerX = 0.638123, centerY = 0.181222, radius = 0.006650, sourceState = sourceAutoComputed, sourceX = 0.660456, sourceY = 0.181222, spotType = heal
+ centerX = 0.796742, centerY = 0.013264, radius = 0.006516, sourceState = sourceAutoComputed, sourceX = 0.781075, sourceY = 0.027264, spotType = heal
+ centerX = 0.019471, centerY = 0.041553, radius = 0.006407, sourceState = sourceAutoComputed, sourceX = 0.036304, sourceY = 0.041553, spotType = heal
+
+
+ 0
+ 0
+ +18
+ -42
+ 0
+ 0
+ 0
+ 0
+ -78
+ 0
+ -24
+ 32
+ 3
+ +0.6
+ 74
+ 0
+ 0
+ 3
+ 0
+ 2
+ 5750
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 761B2C1BEF1BA890ADA86F4EDEEDCC20
+ 53.5666
+ 0
+ 2
+ 0.811756074,0.416356998,0.820405652,0.758753367
+ 0.220619541,0.425669669,0.214266629,0.755766117
+ 761B2C1BEF1BA890ADA86F4EDEEDCC20
+ False
+ 6
+ 1.000000000,0.000000000,0.000000000,0.000000000,1.000000000,0.000000000,0.000000000,0.000000000,1.000000000
+ 1.066300504,0.025984937,-0.025539485,0.010894824,1.088947749,-0.021391182,0.017185023,0.049733695,1.000000000
+ 1.395543247,0.059107101,-0.059107101,0.253149986,1.232556603,-0.162274375,0.290117842,0.101836526,1.000000000
+ 1.001976674,0.000877864,-0.001427269,-0.001975194,1.001976673,-0.000000739,0.000000000,0.000000000,1.000000000
+ 1.085848322,0.046112967,-0.044602696,-0.004298181,1.151298389,-0.028010808,0.000000882,0.088461868,1.000000000
+ 1.083993316,0.041296964,-0.040345298,0.000000000,1.147595490,-0.029382324,0.000000000,0.086295285,1.000000000
+ 151388160
+ 10.1
+ +44
+ 0
+ Auto
+ +44
+ image/jpeg
+ 3
+ 2018-07-27T22:02:03.035
+
+
+ xmp.did:080e0744-eb2c-4fc5-af82-ce438fccb4ab
+ xmp.did:27d0c57c-ea4d-4548-90bc-8dd04bd88f5b
+ xmp.did:427b2bc9-7f5c-47c3-ad92-4f755a696fd4
+ xmp.did:445a0595-2032-4b56-a3aa-3390307f9fbd
+ xmp.did:590d76e7-9ab0-4d3d-b2ec-16ae48d63830
+ xmp.did:6a21a9af-e942-457a-920b-61413cbc9092
+ xmp.did:8948b37d-b6fc-4762-aab9-0aadc0978396
+ xmp.did:bd664069-5d0b-4f13-b44e-f70dd7f2d062
+ xmp.did:c9b36e10-f5af-469b-bbcc-6ff7ea85c522
+ xmp.did:eded9ce4-cfb1-42d8-a847-9c6e00b88dd6
+
+
+ Adobe RGB (1998)
+ 2018-07-27T22:02:03
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T11:31:57+02:00
+ 2018-07-30T11:31:57+02:00
+ 3
+
+
+ adobe:docid:photoshop:4741d4bf-06a2-8642-9e51-58107ae860c0
+ xmp.iid:3716d185-986d-4ce2-a7d7-e4a84227a3b8
+ CBE9E97CB36CBB122E8393CCAFA008AF
+
+
+ adobe:docid:photoshop:60075677-3d34-8641-affe-8bb51d30fed7
+
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/vnd.adobe.photoshop, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:6a21a9af-e942-457a-920b-61413cbc9092
+ Adobe Photoshop Lightroom 6.14 (Macintosh)
+ 2018-07-30T09:15:52+02:00
+
+
+
+
+ converted
+ from image/vnd.adobe.photoshop to application/vnd.adobe.photoshop
+
+
+
+
+ saved
+ /
+ xmp.iid:a88ad3df-b424-4259-a1a7-3cdbfc34de62
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T10:10:44+02:00
+
+
+
+
+ saved
+ /
+ xmp.iid:a7fb9e28-b67d-4cea-b92e-9e506137de16
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T10:58:42+02:00
+
+
+
+
+ converted
+ from application/vnd.adobe.photoshop to image/tiff
+
+
+
+
+ derived
+ converted from application/vnd.adobe.photoshop to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:a2c2087b-0725-4e76-8d47-6763b96e0dca
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T10:58:42+02:00
+
+
+
+
+ saved
+ /
+ xmp.iid:3716d185-986d-4ce2-a7d7-e4a84227a3b8
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T11:31:57+02:00
+
+
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:bda6841d-bc0c-4bb3-9d06-f4fe4906a6fe
+ Adobe Photoshop CC 2018 (Macintosh)
+ 2018-07-30T11:31:57+02:00
+
+
+
+
+ xmp.iid:bda6841d-bc0c-4bb3-9d06-f4fe4906a6fe
+ CBE9E97CB36CBB122E8393CCAFA008AF
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_37_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_37_formatted_compact.xmp
new file mode 100644
index 0000000..62de793
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_37_formatted_compact.xmp
@@ -0,0 +1,797 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.849125 0.347288
+ r 0.007826
+ d 0.851469 0.347475
+ r 0.007824
+ d 0.853814 0.347662
+ r 0.007823
+ d 0.855413 0.347786
+ r 0.007821
+ d 0.857756 0.347969
+ r 0.007818
+ d 0.860099 0.348151
+ d 0.860287 0.348178
+ r 0.007816
+ d 0.862621 0.348511
+ r 0.007814
+ d 0.864955 0.348845
+ r 0.007812
+ d 0.867289 0.349178
+ r 0.007811
+ d 0.868758 0.349533
+ r 0.007809
+ d 0.871070 0.350092
+ r 0.007807
+ d 0.873383 0.350651
+ r 0.007806
+ d 0.874200 0.350826
+ r 0.007804
+ d 0.876518 0.351321
+ r 0.007802
+ d 0.878836 0.351817
+ r 0.007801
+ d 0.880464 0.352185
+ r 0.007799
+ d 0.882237 0.352481
+ r 0.007798
+ d 0.882948 0.352617
+ r 0.007796
+ d 0.885268 0.353059
+ r 0.007795
+ d 0.887588 0.353501
+ d 0.887632 0.353508
+ r 0.007793
+ d 0.889956 0.353888
+ r 0.007792
+ d 0.890735 0.353980
+ r 0.007790
+ d 0.893065 0.354255
+ r 0.007788
+ d 0.895394 0.354530
+ r 0.007787
+ d 0.896062 0.354554
+ r 0.007785
+ d 0.898398 0.354637
+ r 0.007783
+ d 0.900177 0.354754
+ r 0.007781
+ d 0.902509 0.354908
+ r 0.007779
+ d 0.904741 0.355546
+ r 0.007777
+ d 0.907032 0.356201
+ r 0.007775
+ d 0.909324 0.356856
+ r 0.007773
+ d 0.911615 0.357511
+ r 0.007771
+ d 0.913906 0.358166
+ d 0.914001 0.358195
+ r 0.007769
+ d 0.916286 0.358882
+ r 0.007767
+ d 0.918571 0.359570
+ r 0.007765
+ d 0.920855 0.360257
+ r 0.007764
+ d 0.922181 0.360557
+ r 0.007762
+ d 0.924483 0.361079
+ r 0.007759
+ d 0.926785 0.361600
+ r 0.007757
+ d 0.929087 0.362121
+ r 0.007756
+ d 0.929717 0.362249
+ r 0.007754
+ d 0.932022 0.362718
+ r 0.007753
+ d 0.933421 0.363132
+ r 0.007752
+ d 0.934440 0.363353
+ r 0.007750
+ d 0.936742 0.363852
+ r 0.007748
+ d 0.939043 0.364351
+ r 0.007746
+ d 0.941352 0.364726
+ r 0.007745
+ d 0.942369 0.365003
+ r 0.007743
+ d 0.944655 0.365626
+ r 0.007742
+ d 0.945177 0.365761
+ r 0.007740
+ d 0.947465 0.366354
+ r 0.007738
+ d 0.949753 0.366948
+ r 0.007737
+ d 0.950559 0.367149
+ r 0.007735
+ d 0.952848 0.367720
+ r 0.007733
+ d 0.955137 0.368291
+ r 0.007731
+ d 0.957339 0.368914
+ r 0.007729
+ d 0.958665 0.369120
+ r 0.007728
+ d 0.960478 0.369472
+ d 0.960751 0.369534
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.404457 0.347800
+ d 0.405823 0.345588
+ d 0.407189 0.343376
+ d 0.408555 0.341163
+ d 0.409103 0.340276
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ centerX = 0.952933, centerY = 0.326332, radius = 0.007729, sourceState = sourceAutoComputed, sourceX = 0.964600, sourceY = 0.352332, spotType = heal
+ centerX = 0.435560, centerY = 0.429980, radius = 0.006717, sourceState = sourceAutoComputed, sourceX = 0.463893, sourceY = 0.417480, spotType = heal
+ centerX = 0.421605, centerY = 0.311260, radius = 0.006698, sourceState = sourceAutoComputed, sourceX = 0.443605, sourceY = 0.322260, spotType = heal
+ centerX = 0.382711, centerY = 0.286331, radius = 0.006684, sourceState = sourceAutoComputed, sourceX = 0.332044, sourceY = 0.314831, spotType = heal
+ centerX = 0.509684, centerY = 0.212355, radius = 0.006681, sourceState = sourceAutoComputed, sourceX = 0.472017, sourceY = 0.212355, spotType = heal
+ centerX = 0.638123, centerY = 0.181222, radius = 0.006650, sourceState = sourceAutoComputed, sourceX = 0.660456, sourceY = 0.181222, spotType = heal
+ centerX = 0.796742, centerY = 0.013264, radius = 0.006516, sourceState = sourceAutoComputed, sourceX = 0.781075, sourceY = 0.027264, spotType = heal
+ centerX = 0.019471, centerY = 0.041553, radius = 0.006407, sourceState = sourceAutoComputed, sourceX = 0.036304, sourceY = 0.041553, spotType = heal
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ xmp.did:080e0744-eb2c-4fc5-af82-ce438fccb4ab
+ xmp.did:27d0c57c-ea4d-4548-90bc-8dd04bd88f5b
+ xmp.did:427b2bc9-7f5c-47c3-ad92-4f755a696fd4
+ xmp.did:445a0595-2032-4b56-a3aa-3390307f9fbd
+ xmp.did:590d76e7-9ab0-4d3d-b2ec-16ae48d63830
+ xmp.did:6a21a9af-e942-457a-920b-61413cbc9092
+ xmp.did:8948b37d-b6fc-4762-aab9-0aadc0978396
+ xmp.did:bd664069-5d0b-4f13-b44e-f70dd7f2d062
+ xmp.did:c9b36e10-f5af-469b-bbcc-6ff7ea85c522
+ xmp.did:eded9ce4-cfb1-42d8-a847-9c6e00b88dd6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_38.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_38.xmp
new file mode 100644
index 0000000..8c22a75
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_38.xmp
@@ -0,0 +1,256 @@
+
+
+
+
+
+ 191/10
+ 1.0.9
+ 0/1
+ 0
+ EF70-200mm f/4L USM
+ 186
+ 70/1 200/1 0/0 0/0
+ 1835918691
+
+
+
+ image/jpeg
+
+
+
+ 3
+ 2014-05-05T16:49:26.076
+
+
+ xmp.did:75ec9faa-8342-484d-a220-f7a57201ba42
+
+
+ Adobe RGB (1998)
+ 9E9754291B9BEB9C7AF35F23AFE3A4B4
+
+
+
+ 2014-05-05T16:49:26
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:29:10+01:00
+ 2014-05-06T20:29:10+01:00
+ 2
+
+
+
+
+ xmp.did:75ec9faa-8342-484d-a220-f7a57201ba42
+ xmp.iid:23629CF50A2068118083AEA048548252
+ 645AA7AC084369EBCFB438EB793F7F16
+
+ xmp.did:75ec9faa-8342-484d-a220-f7a57201ba42
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/tiff
+
+
+ saved
+ /
+ xmp.iid:75ec9faa-8342-484d-a220-f7a57201ba42
+ Adobe Photoshop Camera Raw 8.2 (Macintosh)
+ 2014-05-06T20:19:57+01:00
+
+
+ saved
+ /
+ xmp.iid:23629CF50A2068118083AEA048548252
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:24:48+01:00
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:24629CF50A2068118083AEA048548252
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:24:48+01:00
+
+
+ saved
+ /
+ xmp.iid:25629CF50A2068118083AEA048548252
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:29:10+01:00
+
+
+
+ xmp.iid:25629CF50A2068118083AEA048548252
+ 645AA7AC084369EBCFB438EB793F7F16
+
+
+
+ True
+ 0
+ 134348800
+ 0
+ 0
+ 0
+ Adobe Standard
+ 6B3E0C3051986B79DFB60C923C3DC656
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0.00
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0
+ 6.7
+ IMG_4649.CR2
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4850
+ +13
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ False
+ 0
+ 134217728
+ 8.2
+ 0
+ 0
+ As Shot
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_38_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_38_formatted_canonical.xmp
new file mode 100644
index 0000000..5f214a5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_38_formatted_canonical.xmp
@@ -0,0 +1,232 @@
+
+
+
+
+ 191/10
+ 1.0.9
+ 0/1
+ 0
+ EF70-200mm f/4L USM
+ 186
+ 70/1 200/1 0/0 0/0
+ 1835918691
+ True
+ 0
+ 134348800
+ 0
+ 0
+ 0
+ Adobe Standard
+ 6B3E0C3051986B79DFB60C923C3DC656
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0.00
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0
+ 6.7
+ IMG_4649.CR2
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4850
+ +13
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ False
+ 0
+ 134217728
+ 8.2
+ 0
+ 0
+ As Shot
+ 0
+ image/jpeg
+ 3
+ 2014-05-05T16:49:26.076
+
+
+ xmp.did:75ec9faa-8342-484d-a220-f7a57201ba42
+
+
+ Adobe RGB (1998)
+ 9E9754291B9BEB9C7AF35F23AFE3A4B4
+ 2014-05-05T16:49:26
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:29:10+01:00
+ 2014-05-06T20:29:10+01:00
+ 2
+
+
+ xmp.did:75ec9faa-8342-484d-a220-f7a57201ba42
+ xmp.iid:23629CF50A2068118083AEA048548252
+ 645AA7AC084369EBCFB438EB793F7F16
+
+
+ xmp.did:75ec9faa-8342-484d-a220-f7a57201ba42
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:75ec9faa-8342-484d-a220-f7a57201ba42
+ Adobe Photoshop Camera Raw 8.2 (Macintosh)
+ 2014-05-06T20:19:57+01:00
+
+
+
+
+ saved
+ /
+ xmp.iid:23629CF50A2068118083AEA048548252
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:24:48+01:00
+
+
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:24629CF50A2068118083AEA048548252
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:24:48+01:00
+
+
+
+
+ saved
+ /
+ xmp.iid:25629CF50A2068118083AEA048548252
+ Adobe Photoshop CS6 (Macintosh)
+ 2014-05-06T20:29:10+01:00
+
+
+
+
+ xmp.iid:25629CF50A2068118083AEA048548252
+ 645AA7AC084369EBCFB438EB793F7F16
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_38_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_38_formatted_compact.xmp
new file mode 100644
index 0000000..c88d8b2
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_38_formatted_compact.xmp
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ xmp.did:75ec9faa-8342-484d-a220-f7a57201ba42
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_39.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_39.xmp
new file mode 100644
index 0000000..3b66912
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_39.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_39_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_39_formatted_canonical.xmp
new file mode 100644
index 0000000..e0b6c54
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_39_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 1
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_39_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_39_formatted_compact.xmp
new file mode 100644
index 0000000..be6643b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_39_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_3_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_3_formatted_canonical.xmp
new file mode 100644
index 0000000..2ed4b5a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_3_formatted_canonical.xmp
@@ -0,0 +1,627 @@
+
+
+
+
+ True
+ 0/256
+ True
+ OLYMPUS M.30mm F3.5 Macro
+ 30/1 30/1 35/10 35/10
+ ACBA37456
+ BHXB09700
+ True
+ 1
+ 134348800
+ +10
+ -15
+ +25
+ Camera Natural
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+ 0.702472
+ +100
+ true
+ 0.345434
+ 1.000000
+ +50
+ 0.519469
+ 0
+ 0.448111
+ +2
+ Mask/CircularGradient
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.286750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ -16
+ 25
+ 50
+ 50
+ 0
+ False
+ -6.96018
+ 0.927253
+ 0
+ 0.16874
+ 0.683865
+ 0.255126
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.471141
+ 0.939926
+ 1.000000
+ Mask/Gradient
+ 0.447089
+ 0.655993
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.007750
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.100750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -1.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ 0
+ -10
+ -10
+ True
+ True
+ -84
+ 0
+ +5
+ +18
+ +15
+ 0
+ +30
+ +8
+ -48
+ 0
+ 1
+ LensDefaults
+ +1
+ -10
+ -47
+ -25
+ -6
+ -10
+ -20
+ 0
+ 0
+ 50
+ 10
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+
+
+ d 0.610881 0.481687
+ d 0.620105 0.534419
+ d 0.625388 0.588143
+ d 0.625680 0.642374
+ d 0.625680 0.696612
+ d 0.629284 0.750435
+ d 0.633725 0.800150
+ d 0.642095 0.747157
+ d 0.645528 0.693143
+ d 0.647145 0.638948
+ d 0.647245 0.584723
+ d 0.644364 0.530632
+ d 0.647204 0.582074
+ d 0.650448 0.636140
+ d 0.653721 0.690203
+ d 0.655921 0.741574
+ d 0.653779 0.687442
+ d 0.646901 0.633994
+ d 0.637894 0.581127
+ d 0.628722 0.528295
+ d 0.625328 0.540444
+ d 0.624725 0.594676
+ d 0.625022 0.648914
+ d 0.626352 0.607175
+ d 0.623402 0.553079
+ d 0.616672 0.500606
+ d 0.616949 0.554842
+ d 0.616428 0.550300
+ d 0.607216 0.497549
+ d 0.596753 0.445147
+ d 0.600021 0.478894
+ d 0.598434 0.454855
+ d 0.581926 0.409236
+ d 0.590936 0.461899
+ d 0.604927 0.512212
+ d 0.610550 0.562039
+ d 0.620352 0.614649
+ d 0.626943 0.668152
+ d 0.631716 0.721999
+ d 0.641849 0.774397
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.242839 0.820612
+ d 0.237692 0.834207
+ d 0.237692 0.779969
+ d 0.237928 0.791930
+ d 0.240202 0.826093
+ d 0.241552 0.839256
+ d 0.240816 0.837668
+ d 0.231901 0.842783
+ d 0.230272 0.788652
+ d 0.224501 0.773922
+ d 0.224501 0.719683
+ d 0.225483 0.704995
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.226110 0.664020
+ d 0.232486 0.717588
+ d 0.232799 0.670566
+ d 0.233025 0.724795
+ d 0.234153 0.699187
+ d 0.231798 0.712948
+ d 0.221593 0.682138
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.217424 0.649862
+ d 0.223416 0.703495
+ d 0.231698 0.656464
+ d 0.233487 0.705027
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.236083 0.670884
+ d 0.239606 0.724917
+ d 0.238537 0.687193
+ d 0.236000 0.673476
+ d 0.235129 0.727702
+ d 0.236083 0.684962
+
+
+ 0.687000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.229327 0.362420
+ d 0.224352 0.416251
+ d 0.239816 0.377775
+ d 0.236548 0.408153
+ d 0.237542 0.423037
+ d 0.244602 0.416914
+ d 0.249595 0.394578
+ d 0.242510 0.447946
+ d 0.238985 0.501936
+ d 0.240338 0.544066
+ d 0.238908 0.597662
+ d 0.238075 0.651889
+ d 0.237370 0.706067
+ d 0.231901 0.758224
+ d 0.231963 0.811572
+ d 0.237586 0.865236
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.640800 0.779855
+ d 0.645762 0.726080
+ d 0.648727 0.672068
+ d 0.646492 0.617994
+ d 0.639456 0.564592
+ d 0.631113 0.511529
+ d 0.618556 0.459987
+ d 0.609974 0.429956
+ d 0.621597 0.481932
+ d 0.633149 0.533923
+ d 0.628719 0.502499
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.622463 0.479542
+ d 0.623864 0.533748
+ d 0.627279 0.587781
+ d 0.624503 0.575236
+ d 0.618239 0.521646
+ d 0.616598 0.504911
+ d 0.623071 0.558458
+ d 0.626645 0.546009
+ d 0.624722 0.491993
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.614420 0.446507
+ d 0.625375 0.498742
+ d 0.636331 0.550977
+ d 0.628723 0.513678
+ d 0.616257 0.462099
+ d 0.599426 0.412752
+ d 0.574708 0.369997
+ d 0.590505 0.388526
+ d 0.567561 0.357011
+ d 0.544818 0.350241
+ d 0.569429 0.363618
+ d 0.537043 0.338195
+ d 0.571237 0.363120
+ d 0.588229 0.410874
+ d 0.602036 0.461893
+ d 0.607968 0.515512
+ d 0.610459 0.569562
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.611203 0.580790
+ d 0.613860 0.634912
+ d 0.619654 0.688584
+ d 0.628727 0.741443
+ d 0.640476 0.793359
+ d 0.655437 0.843586
+ d 0.680571 0.885792
+ d 0.714799 0.914002
+ d 0.737572 0.921030
+
+
+ 0.918000
+ 1.000000
+ 0.135597
+ Mask/Paint
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.069750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 60
+ 14
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ -38
+ 50
+ 0
+ 50
+ 0
+ 1
+ 6.7
+ +10
+ 0
+ +5
+ -16
+ -65
+ -30
+ -18
+ -28
+ -10
+ -19
+ -31
+ 0
+ +15
+ 25
+ 81
+ +1.2
+ 58
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4650
+ +11
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 63, 64
+ 125, 125
+ 192, 191
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 26
+ 119, 124
+ 181, 200
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 25
+ 119, 125
+ 182, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 25
+ 118, 124
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 10.1
+ 0
+ 0
+ As Shot
+ 0
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+ image/jpeg
+ 2020-04-30T15:08:15
+ 2020-04-30T15:08:15
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2020-04-30T21:24:18+03:00
+ 2020-04-30T21:24:18+03:00
+ 2
+
+
+ 4086326A817940C408F83F2277090335
+ 4086326A817940C408F83F2277090335
+
+
+ xmp.did:ff982fc2-4308-7048-beec-991da4c7c819
+
+
+
+
+ derived
+ converted from image/x-olympus-raw to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:ff982fc2-4308-7048-beec-991da4c7c819
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2020-04-30T21:24:18+03:00
+
+
+
+
+ xmp.iid:ff982fc2-4308-7048-beec-991da4c7c819
+ 4086326A817940C408F83F2277090335
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_3_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_3_formatted_compact.xmp
new file mode 100644
index 0000000..22c4af1
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_3_formatted_compact.xmp
@@ -0,0 +1,612 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.610881 0.481687
+ d 0.620105 0.534419
+ d 0.625388 0.588143
+ d 0.625680 0.642374
+ d 0.625680 0.696612
+ d 0.629284 0.750435
+ d 0.633725 0.800150
+ d 0.642095 0.747157
+ d 0.645528 0.693143
+ d 0.647145 0.638948
+ d 0.647245 0.584723
+ d 0.644364 0.530632
+ d 0.647204 0.582074
+ d 0.650448 0.636140
+ d 0.653721 0.690203
+ d 0.655921 0.741574
+ d 0.653779 0.687442
+ d 0.646901 0.633994
+ d 0.637894 0.581127
+ d 0.628722 0.528295
+ d 0.625328 0.540444
+ d 0.624725 0.594676
+ d 0.625022 0.648914
+ d 0.626352 0.607175
+ d 0.623402 0.553079
+ d 0.616672 0.500606
+ d 0.616949 0.554842
+ d 0.616428 0.550300
+ d 0.607216 0.497549
+ d 0.596753 0.445147
+ d 0.600021 0.478894
+ d 0.598434 0.454855
+ d 0.581926 0.409236
+ d 0.590936 0.461899
+ d 0.604927 0.512212
+ d 0.610550 0.562039
+ d 0.620352 0.614649
+ d 0.626943 0.668152
+ d 0.631716 0.721999
+ d 0.641849 0.774397
+
+
+
+
+
+
+
+
+ d 0.242839 0.820612
+ d 0.237692 0.834207
+ d 0.237692 0.779969
+ d 0.237928 0.791930
+ d 0.240202 0.826093
+ d 0.241552 0.839256
+ d 0.240816 0.837668
+ d 0.231901 0.842783
+ d 0.230272 0.788652
+ d 0.224501 0.773922
+ d 0.224501 0.719683
+ d 0.225483 0.704995
+
+
+
+
+
+
+
+
+ d 0.226110 0.664020
+ d 0.232486 0.717588
+ d 0.232799 0.670566
+ d 0.233025 0.724795
+ d 0.234153 0.699187
+ d 0.231798 0.712948
+ d 0.221593 0.682138
+
+
+
+
+
+
+
+
+ d 0.217424 0.649862
+ d 0.223416 0.703495
+ d 0.231698 0.656464
+ d 0.233487 0.705027
+
+
+
+
+
+
+
+
+ d 0.236083 0.670884
+ d 0.239606 0.724917
+ d 0.238537 0.687193
+ d 0.236000 0.673476
+ d 0.235129 0.727702
+ d 0.236083 0.684962
+
+
+
+
+
+
+
+
+ d 0.229327 0.362420
+ d 0.224352 0.416251
+ d 0.239816 0.377775
+ d 0.236548 0.408153
+ d 0.237542 0.423037
+ d 0.244602 0.416914
+ d 0.249595 0.394578
+ d 0.242510 0.447946
+ d 0.238985 0.501936
+ d 0.240338 0.544066
+ d 0.238908 0.597662
+ d 0.238075 0.651889
+ d 0.237370 0.706067
+ d 0.231901 0.758224
+ d 0.231963 0.811572
+ d 0.237586 0.865236
+
+
+
+
+
+
+
+
+ d 0.640800 0.779855
+ d 0.645762 0.726080
+ d 0.648727 0.672068
+ d 0.646492 0.617994
+ d 0.639456 0.564592
+ d 0.631113 0.511529
+ d 0.618556 0.459987
+ d 0.609974 0.429956
+ d 0.621597 0.481932
+ d 0.633149 0.533923
+ d 0.628719 0.502499
+
+
+
+
+
+
+
+
+ d 0.622463 0.479542
+ d 0.623864 0.533748
+ d 0.627279 0.587781
+ d 0.624503 0.575236
+ d 0.618239 0.521646
+ d 0.616598 0.504911
+ d 0.623071 0.558458
+ d 0.626645 0.546009
+ d 0.624722 0.491993
+
+
+
+
+
+
+
+
+ d 0.614420 0.446507
+ d 0.625375 0.498742
+ d 0.636331 0.550977
+ d 0.628723 0.513678
+ d 0.616257 0.462099
+ d 0.599426 0.412752
+ d 0.574708 0.369997
+ d 0.590505 0.388526
+ d 0.567561 0.357011
+ d 0.544818 0.350241
+ d 0.569429 0.363618
+ d 0.537043 0.338195
+ d 0.571237 0.363120
+ d 0.588229 0.410874
+ d 0.602036 0.461893
+ d 0.607968 0.515512
+ d 0.610459 0.569562
+
+
+
+
+
+
+
+
+ d 0.611203 0.580790
+ d 0.613860 0.634912
+ d 0.619654 0.688584
+ d 0.628727 0.741443
+ d 0.640476 0.793359
+ d 0.655437 0.843586
+ d 0.680571 0.885792
+ d 0.714799 0.914002
+ d 0.737572 0.921030
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 63, 64
+ 125, 125
+ 192, 191
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 26
+ 119, 124
+ 181, 200
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 25
+ 119, 125
+ 182, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 25
+ 118, 124
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_4.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_4.xmp
new file mode 100644
index 0000000..649c5c9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_4.xmp
@@ -0,0 +1,323 @@
+
+
+
+
+
+ 67/100
+ 1.1.5
+ 0/1
+ 0
+ True
+ EF-S18-55mm f/3.5-5.6 IS STM
+ 4146
+ 18/1 55/1 0/0 0/0
+ 0000070a01
+ 322034001175
+
+
+
+ image/jpeg
+
+
+
+ EF-S18-55mm f/3.5-5.6 IS STM
+
+
+
+ 2019-12-23T01:28:28.23
+
+
+
+ 2019-12-23T01:28:28.23
+ Adobe Photoshop Lightroom Classic 9.1 (Macintosh)
+ 2019-12-23T13:28:07-05:00
+ 2019-12-23T13:28:07-05:00
+ 1
+
+
+
+
+ D749B1BD13ECB96B04E98B6E2C04E2ED
+ D749B1BD13ECB96B04E98B6E2C04E2ED
+
+ xmp.did:61d558ea-ab7f-469a-8e30-064f02333d6c
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:61d558ea-ab7f-469a-8e30-064f02333d6c
+ Adobe Photoshop Lightroom Classic 9.1 (Macintosh)
+ 2019-12-23T13:28:07-05:00
+
+
+
+ xmp.iid:61d558ea-ab7f-469a-8e30-064f02333d6c
+ D749B1BD13ECB96B04E98B6E2C04E2ED
+ IMG_9499.CR2
+
+
+
+ True
+ 1
+ -9
+ -10
+ -10
+ Adobe Standard
+ BA45C872F6A5D11497D00CBA08D5783F
+ 0
+ 25
+ 50
+ 50
+ -18
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.95
+ 22
+ 50
+ 3847824574
+ 45
+ +50
+ -25
+ False
+ True
+ -94
+ 0
+ +2
+ -10
+ 0
+ -15
+ 0
+ -18
+ -9
+ 0
+ 0
+ LensDefaults
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 12.1
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+ 0
+ -70
+ +17
+ 0
+ -39
+ 0
+ -23
+ -21
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ IMG_9499.CR2
+ +46
+ +5
+ -35
+ -51
+ -19
+ -17
+ 0
+ +16
+ 0
+ +5
+ -15
+ 0
+ +55
+ 25
+ 0
+ +1.0
+ 35
+ 0
+ 34
+ 0
+ 52
+ 20
+ 4659
+ 0
+ +14
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 59, 62
+ 177, 163
+ 255, 211
+
+
+
+
+ 0, 0
+ 39, 13
+ 87, 65
+ 123, 124
+ 183, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 14
+ 83, 59
+ 123, 126
+ 172, 185
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 12
+ 84, 58
+ 123, 125
+ 186, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 12.1
+ -4
+ 0
+ Custom
+ -99
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_40.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_40.xmp
new file mode 100644
index 0000000..5804350
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_40.xmp
@@ -0,0 +1,345 @@
+
+
+
+
+
+ 398/100
+ 24948
+ 70.0-200.0 mm f/2.8
+ 119
+ 700/10 2000/10 28/10 28/10
+ 6031249
+
+
+
+
+
+ Peter Neumann
+
+
+ image/jpeg
+
+
+ Peter Neumann
+
+
+
+
+
+ 3
+ 2019-06-27T09:25:17.002
+ sRGB IEC61966-2.1
+
+
+
+ 2019-06-27T09:25:17
+ NIKON D500 Ver.1.20
+ 2019-11-03T16:20:09+01:00
+ 2019-11-03T16:20:09+01:00
+ 3
+
+
+
+
+ xmp.did:456565f1-e08e-ac4e-a993-e209f7679648
+ xmp.iid:603e738c-da47-4a45-87ff-77554b8fdacd
+ 0069C75FF55EA6B570989A33B81CA6FC
+
+ adobe:docid:photoshop:ca08ac35-b7f3-8144-b247-4add7d17bbd0
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/tiff
+
+
+ saved
+ /
+ xmp.iid:456565f1-e08e-ac4e-a993-e209f7679648
+ Adobe Photoshop Camera Raw 11.4.1 (Windows)
+ 2019-11-03T16:09:15+01:00
+
+
+ saved
+ /
+ xmp.iid:603e738c-da47-4a45-87ff-77554b8fdacd
+ Adobe Photoshop CC 2019 (Windows)
+ 2019-11-03T16:20:09+01:00
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+ saved
+ /
+ xmp.iid:a75f4b1a-2e27-ec4d-beca-40aec72796c8
+ Adobe Photoshop CC 2019 (Windows)
+ 2019-11-03T16:20:09+01:00
+
+
+
+ xmp.iid:a75f4b1a-2e27-ec4d-beca-40aec72796c8
+ 0069C75FF55EA6B570989A33B81CA6FC
+
+
+
+ True
+ 0
+ +59
+ 0
+ 0
+ Adobe Standard
+ ABB0410514417CFA55F22BBA7035E8AB
+ +78
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 0.926681
+ 1
+ 2
+ 0
+ 0.876252
+ 0.050429
+ 3
+ 3
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +11
+ 0.00
+ 0
+ 0
+ 0
+ True
+ True
+ +4
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+ 1.000000
+ © 2018 Adobe Systems, Inc.
+
+
+ Profiles
+
+
+ Adobe Color
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.4.1
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ DSC_2304.NEF
+ 0
+ 0
+ +19
+ +38
+ +13
+ +19
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +80
+ 15
+ 45
+ +1.7
+ 78
+ 0
+ 0
+ 0
+ 0
+ 0
+ 6400
+ 0
+ +24
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Medium Contrast
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.4.1
+ 0
+ 0
+ Custom
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_40_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_40_formatted_canonical.xmp
new file mode 100644
index 0000000..dc8ff1e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_40_formatted_canonical.xmp
@@ -0,0 +1,323 @@
+
+
+
+
+ 398/100
+ 24948
+ 70.0-200.0 mm f/2.8
+ 119
+ 700/10 2000/10 28/10 28/10
+ 6031249
+ True
+ 0
+ +59
+ 0
+ 0
+ Adobe Standard
+ ABB0410514417CFA55F22BBA7035E8AB
+ +78
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 0.926681
+ 1
+ 2
+ 0
+ 0.876252
+ 0.050429
+ 3
+ 3
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +11
+ 0.00
+ 0
+ 0
+ 0
+ True
+ True
+ +4
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+ 1.000000
+ © 2018 Adobe Systems, Inc.
+
+
+ Profiles
+
+
+ Adobe Color
+
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.4.1
+
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ DSC_2304.NEF
+ 0
+ 0
+ +19
+ +38
+ +13
+ +19
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +80
+ 15
+ 45
+ +1.7
+ 78
+ 0
+ 0
+ 0
+ 0
+ 0
+ 6400
+ 0
+ +24
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Medium Contrast
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.4.1
+ 0
+ 0
+ Custom
+ 0
+
+
+ Peter Neumann
+
+
+ image/jpeg
+
+
+ Peter Neumann
+
+
+ 3
+ 2019-06-27T09:25:17.002
+ sRGB IEC61966-2.1
+ 2019-06-27T09:25:17
+ NIKON D500 Ver.1.20
+ 2019-11-03T16:20:09+01:00
+ 2019-11-03T16:20:09+01:00
+ 3
+
+
+ xmp.did:456565f1-e08e-ac4e-a993-e209f7679648
+ xmp.iid:603e738c-da47-4a45-87ff-77554b8fdacd
+ 0069C75FF55EA6B570989A33B81CA6FC
+
+
+ adobe:docid:photoshop:ca08ac35-b7f3-8144-b247-4add7d17bbd0
+
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:456565f1-e08e-ac4e-a993-e209f7679648
+ Adobe Photoshop Camera Raw 11.4.1 (Windows)
+ 2019-11-03T16:09:15+01:00
+
+
+
+
+ saved
+ /
+ xmp.iid:603e738c-da47-4a45-87ff-77554b8fdacd
+ Adobe Photoshop CC 2019 (Windows)
+ 2019-11-03T16:20:09+01:00
+
+
+
+
+ converted
+ from image/tiff to image/jpeg
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg
+
+
+
+
+ saved
+ /
+ xmp.iid:a75f4b1a-2e27-ec4d-beca-40aec72796c8
+ Adobe Photoshop CC 2019 (Windows)
+ 2019-11-03T16:20:09+01:00
+
+
+
+
+ xmp.iid:a75f4b1a-2e27-ec4d-beca-40aec72796c8
+ 0069C75FF55EA6B570989A33B81CA6FC
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_40_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_40_formatted_compact.xmp
new file mode 100644
index 0000000..5893744
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_40_formatted_compact.xmp
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+ Profiles
+
+
+
+
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ Peter Neumann
+
+
+
+
+ Peter Neumann
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_41.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_41.xmp
new file mode 100644
index 0000000..3b66912
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_41.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_41_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_41_formatted_canonical.xmp
new file mode 100644
index 0000000..e0b6c54
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_41_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 1
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_41_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_41_formatted_compact.xmp
new file mode 100644
index 0000000..be6643b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_41_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_42.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_42.xmp
new file mode 100644
index 0000000..75dbb55
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_42.xmp
@@ -0,0 +1,334 @@
+
+
+
+
+
+ 569/100
+ True
+ 1.1.2
+ 0/1
+ 0
+ True
+ EF100-400mm f/4.5-5.6L IS II USM
+ 747
+ 100/1 400/1 0/0 0/0
+ 3610001521
+ 058021001207
+ True
+
+
+
+
+
+ Zdenek & Eva Machackovi
+
+
+ image/jpeg
+
+
+ ZEphoto.zenfolio.com
+
+
+
+
+
+ EF100-400mm f/4.5-5.6L IS II USM
+
+
+
+ 2019-10-03T13:44:17.09
+
+
+
+ 2019-10-03T13:44:17.09
+ Adobe Photoshop Lightroom Classic 8.4.1 (Windows)
+ 2019-10-09T20:46:41+02:00
+ 2019-10-09T20:46:41+02:00
+ 4
+
+
+
+
+ 65FEF5F0166319A4F14A4323DC9D3BAF
+ 65FEF5F0166319A4F14A4323DC9D3BAF
+
+ xmp.did:a7ef1345-23e4-b747-9bd5-603d8a177783
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:a7ef1345-23e4-b747-9bd5-603d8a177783
+ Adobe Photoshop Lightroom Classic 8.4.1 (Windows)
+ 2019-10-09T20:46:41+02:00
+
+
+
+ xmp.iid:a7ef1345-23e4-b747-9bd5-603d8a177783
+ 65FEF5F0166319A4F14A4323DC9D3BAF
+ _D2_2886.CR2
+
+
+
+ True
+ 1
+ -24
+ 0
+ 0
+ Adobe Standard
+ 986DA3807E87AD9D00B35EFF80D467B9
+ +13
+ 25
+ 50
+ 50
+ +7
+ False
+ 0
+ 0.891957
+ 0
+ 0.2928
+ 1
+ 0.033613
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.63
+ 0
+ 0
+ 0
+ True
+ True
+ -77
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ A4D0BA96DE80CADD0EFEC7ADBE403FCD
+ 100
+ 1
+ Canon EOS-1Ds Mark III (Canon EF 100-400mm f4.5-5.6L IS II USM) - RAW.lcp
+ Adobe (Canon EF 100-400mm f/4.5-5.6L IS II USM)
+ LensDefaults
+ 100
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.4.1
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 25
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _D2_2886.CR2
+ 0
+ 0
+ -3
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +87
+ 25
+ 87
+ +1.0
+ 54
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4500
+ +24
+ +9
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 65, 59
+ 193, 198
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.4.1
+ +13
+ 0
+ As Shot
+ +49
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_42_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_42_formatted_canonical.xmp
new file mode 100644
index 0000000..02ac6f7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_42_formatted_canonical.xmp
@@ -0,0 +1,301 @@
+
+
+
+
+ 569/100
+ True
+ 1.1.2
+ 0/1
+ 0
+ True
+ EF100-400mm f/4.5-5.6L IS II USM
+ 747
+ 100/1 400/1 0/0 0/0
+ 3610001521
+ 058021001207
+ True
+ True
+ 1
+ -24
+ 0
+ 0
+ Adobe Standard
+ 986DA3807E87AD9D00B35EFF80D467B9
+ +13
+ 25
+ 50
+ 50
+ +7
+ False
+ 0
+ 0.891957
+ 0
+ 0.2928
+ 1
+ 0.033613
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.63
+ 0
+ 0
+ 0
+ True
+ True
+ -77
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 100
+ A4D0BA96DE80CADD0EFEC7ADBE403FCD
+ 100
+ 1
+ Canon EOS-1Ds Mark III (Canon EF 100-400mm f4.5-5.6L IS II USM) - RAW.lcp
+ Adobe (Canon EF 100-400mm f/4.5-5.6L IS II USM)
+ LensDefaults
+ 100
+
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.4.1
+
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 25
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ _D2_2886.CR2
+ 0
+ 0
+ -3
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +87
+ 25
+ 87
+ +1.0
+ 54
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4500
+ +24
+ +9
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 65, 59
+ 193, 198
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.4.1
+ +13
+ 0
+ As Shot
+ +49
+
+
+ Zdenek & Eva Machackovi
+
+
+ image/jpeg
+
+
+ ZEphoto.zenfolio.com
+
+
+ EF100-400mm f/4.5-5.6L IS II USM
+ 2019-10-03T13:44:17.09
+ 2019-10-03T13:44:17.09
+ Adobe Photoshop Lightroom Classic 8.4.1 (Windows)
+ 2019-10-09T20:46:41+02:00
+ 2019-10-09T20:46:41+02:00
+ 4
+
+
+ 65FEF5F0166319A4F14A4323DC9D3BAF
+ 65FEF5F0166319A4F14A4323DC9D3BAF
+
+
+ xmp.did:a7ef1345-23e4-b747-9bd5-603d8a177783
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:a7ef1345-23e4-b747-9bd5-603d8a177783
+ Adobe Photoshop Lightroom Classic 8.4.1 (Windows)
+ 2019-10-09T20:46:41+02:00
+
+
+
+
+ xmp.iid:a7ef1345-23e4-b747-9bd5-603d8a177783
+ 65FEF5F0166319A4F14A4323DC9D3BAF
+ _D2_2886.CR2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_42_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_42_formatted_compact.xmp
new file mode 100644
index 0000000..95af5d8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_42_formatted_compact.xmp
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+
+
+ Profiles
+
+
+
+
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 65, 59
+ 193, 198
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ Zdenek & Eva Machackovi
+
+
+
+
+ ZEphoto.zenfolio.com
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_43.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_43.xmp
new file mode 100644
index 0000000..3c3bfe6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_43.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_43_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_43_formatted_canonical.xmp
new file mode 100644
index 0000000..b4822a8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_43_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 4
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_43_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_43_formatted_compact.xmp
new file mode 100644
index 0000000..98d7ab9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_43_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_44.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_44.xmp
new file mode 100644
index 0000000..80c6ac9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_44.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_44_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_44_formatted_canonical.xmp
new file mode 100644
index 0000000..e43c7a5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_44_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_44_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_44_formatted_compact.xmp
new file mode 100644
index 0000000..81e1785
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_44_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_45.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_45.xmp
new file mode 100644
index 0000000..70386ae
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_45.xmp
@@ -0,0 +1,691 @@
+
+
+
+
+
+ FE 24-240mm F3.5-6.3 OSS
+ 240/10 2400/10 35/10 63/10
+
+
+
+ image/jpeg
+
+
+
+ 2017-07-17T18:21:14
+
+
+
+ 2017-07-17T18:21:14
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T22:04:13-03:00
+ 2017-07-17T22:04:13-03:00
+ 2
+
+
+
+
+ xmp.did:b593b3ed-ff2f-4e8e-95d5-7cc5988c01c5
+ xmp.iid:b593b3ed-ff2f-4e8e-95d5-7cc5988c01c5
+ 6BE69D49EC5B5C820D2EEE864415D602
+
+ xmp.did:1a499823-4165-4149-9355-5038b48eda98
+
+
+
+ converted
+ from image/x-sony-arw to image/dng
+
+
+ saved
+ /metadata
+ xmp.iid:b593b3ed-ff2f-4e8e-95d5-7cc5988c01c5
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T20:37-03:00
+
+
+ derived
+ converted from image/dng to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:1a499823-4165-4149-9355-5038b48eda98
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T22:04:13-03:00
+
+
+ saved
+ /metadata
+ xmp.iid:e5101770-6dc9-4c2d-93ba-7e0dddb726be
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T22:04:13-03:00
+
+
+
+ xmp.iid:e5101770-6dc9-4c2d-93ba-7e0dddb726be
+ 6BE69D49EC5B5C820D2EEE864415D602
+
+
+
+ True
+ 1
+ 134348800
+ +7
+ -11
+ 0
+ Adobe Standard
+ +5
+ 25
+ 50
+ 50
+ +26
+ False
+ 0
+ 0.952836
+ 0
+ 0.0051
+ 0.852836
+ 0.089087
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +0.59
+ 15
+ 50
+ 1866158102
+ 45
+ +44
+ 0
+ True
+ True
+ -82
+ +10
+ -23
+ 0
+ +20
+ +5
+ +20
+ +10
+ 0
+ 0
+ 100
+ 0E0E0483ED048452CE6CC01CB9D7D893
+ 100
+ 0
+ SONY (Sony FE 24-240mm F3.5-6.3 OSS) - RAW.lcp
+ Adobe (Sony FE 24-240mm F3.5-6.3 OSS)
+ LensDefaults
+ 100
+ -12
+ +59
+ 0
+ 0
+ +5
+ 0
+ 0
+ 0
+ 0
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.092338
+
+
+ d 0.550077 0.453913
+ r 0.106822
+ d 0.520538 0.468930
+ r 0.106808
+ d 0.500725 0.505721
+ r 0.106833
+ d 0.496680 0.552137
+ r 0.106876
+ d 0.519958 0.580808
+ r 0.106895
+ d 0.548101 0.566140
+ r 0.106881
+ d 0.560024 0.524490
+ r 0.106884
+ d 0.561107 0.476508
+ r 0.106899
+ d 0.552378 0.434688
+ r 0.106846
+ d 0.523235 0.444702
+ r 0.106816
+ d 0.499427 0.476094
+ r 0.106824
+ d 0.480304 0.514348
+ r 0.106858
+ d 0.475552 0.559611
+ r 0.106897
+ d 0.492413 0.595464
+ r 0.106870
+ d 0.518960 0.575597
+ r 0.106846
+ d 0.536703 0.536319
+ r 0.106861
+ d 0.546815 0.491035
+ r 0.106893
+ d 0.554394 0.444487
+ r 0.106968
+ d 0.555413 0.396484
+ r 0.106973
+ d 0.543178 0.384028
+ r 0.106879
+ d 0.525867 0.424331
+ r 0.106823
+ d 0.511626 0.467138
+ r 0.106810
+ d 0.503710 0.512444
+ r 0.106845
+ d 0.513228 0.558261
+ r 0.106928
+ d 0.540532 0.576265
+ r 0.107022
+ d 0.571590 0.588377
+ r 0.107117
+ d 0.602716 0.600434
+ r 0.107259
+ d 0.632682 0.596823
+ r 0.107191
+ d 0.631266 0.549494
+ r 0.107117
+ d 0.622830 0.503102
+ r 0.107075
+ d 0.611411 0.458268
+ r 0.106992
+ d 0.590011 0.449967
+ r 0.107005
+ d 0.598077 0.496484
+ r 0.107091
+ d 0.613109 0.538498
+ r 0.107226
+ d 0.633399 0.575002
+ r 0.107417
+ d 0.664455 0.571043
+ r 0.107397
+ d 0.666784 0.535788
+ r 0.107252
+ d 0.642236 0.504925
+ r 0.107108
+ d 0.617137 0.474806
+ r 0.106982
+ d 0.588570 0.454908
+ r 0.106895
+ d 0.566615 0.482976
+ r 0.106887
+ d 0.560958 0.529939
+ r 0.106952
+ d 0.570297 0.571515
+ r 0.106964
+ d 0.581924 0.526779
+ r 0.106985
+ d 0.592769 0.481572
+ r 0.107060
+ d 0.599760 0.434648
+ r 0.107143
+ d 0.602691 0.387180
+ r 0.107148
+ d 0.582107 0.353694
+ r 0.107091
+ d 0.551252 0.343667
+ r 0.106978
+ d 0.528428 0.372647
+ r 0.106885
+ d 0.522544 0.419049
+ r 0.106828
+ d 0.526121 0.464519
+ r 0.106892
+ d 0.554594 0.446531
+ r 0.106998
+ d 0.579749 0.417010
+ r 0.107063
+ d 0.578712 0.381745
+ r 0.107047
+ d 0.588228 0.406282
+ r 0.107212
+ d 0.610614 0.372765
+ r 0.107236
+ d 0.595553 0.340617
+ r 0.107125
+ d 0.564782 0.343689
+ r 0.106988
+ d 0.542013 0.376746
+ r 0.106882
+ d 0.525553 0.417782
+ r 0.106948
+ d 0.544006 0.394959
+ r 0.106969
+ d 0.528907 0.376524
+ r 0.106971
+ d 0.497175 0.369564
+ r 0.106902
+ d 0.509024 0.400551
+ r 0.106944
+ d 0.525368 0.384667
+ r 0.106898
+ d 0.508112 0.403409
+ r 0.106845
+ d 0.503437 0.450597
+ r 0.106814
+ d 0.501593 0.498503
+ r 0.106823
+ d 0.516832 0.537627
+ r 0.106858
+ d 0.547423 0.531031
+ r 0.106877
+ d 0.559390 0.488709
+ r 0.106910
+ d 0.557095 0.441228
+ r 0.106929
+ d 0.541816 0.403862
+ r 0.106870
+ d 0.517747 0.421556
+ r 0.106822
+ d 0.508947 0.467700
+ r 0.106815
+ d 0.503279 0.514841
+ r 0.106849
+ d 0.499223 0.562437
+ r 0.106909
+ d 0.485215 0.599547
+ r 0.106897
+ d 0.490295 0.592247
+ r 0.106860
+ d 0.517689 0.567318
+ r 0.106862
+ d 0.544311 0.540665
+ r 0.106884
+ d 0.560416 0.502504
+ r 0.106917
+ d 0.566212 0.455446
+ r 0.106988
+ d 0.566901 0.407415
+ r 0.107085
+ d 0.567243 0.359335
+ r 0.106975
+ d 0.556458 0.395924
+ r 0.106910
+ d 0.552261 0.443356
+ r 0.106865
+ d 0.549546 0.491197
+ r 0.106897
+ d 0.559145 0.534934
+ r 0.106976
+ d 0.577398 0.571906
+ r 0.107068
+ d 0.604027 0.566485
+ r 0.107019
+ d 0.600219 0.518930
+
+
+ 0.200000
+ 1.000000
+ 0.106873
+ Mask/Paint
+
+
+ 0.092338
+
+
+ d 0.566661 0.538366
+ r 0.106882
+ d 0.541813 0.567195
+ r 0.106929
+ d 0.536683 0.601096
+ r 0.106970
+ d 0.566832 0.588891
+ r 0.106923
+ d 0.568658 0.550795
+ r 0.106863
+ d 0.538016 0.553920
+ r 0.106882
+ d 0.512408 0.581818
+ r 0.106949
+ d 0.490852 0.617374
+ r 0.107024
+ d 0.482470 0.646244
+ r 0.106953
+ d 0.510312 0.622943
+ r 0.106896
+ d 0.530616 0.585900
+ r 0.106886
+ d 0.544947 0.542953
+ r 0.106876
+ d 0.559278 0.500006
+ r 0.106922
+ d 0.567467 0.453660
+ r 0.106886
+ d 0.552312 0.453384
+ r 0.106839
+ d 0.527834 0.483247
+ r 0.106840
+ d 0.507702 0.520587
+ r 0.106845
+ d 0.487778 0.558154
+ r 0.106927
+ d 0.473313 0.601001
+ r 0.106976
+ d 0.490174 0.630790
+ r 0.106980
+ d 0.520269 0.631014
+ r 0.106927
+ d 0.540365 0.594812
+ r 0.106889
+ d 0.551051 0.550057
+ r 0.106868
+ d 0.556893 0.502988
+ r 0.106833
+ d 0.537076 0.498236
+ r 0.106824
+ d 0.514421 0.532173
+ r 0.106862
+ d 0.495516 0.570725
+ r 0.106946
+ d 0.481764 0.613786
+ r 0.106964
+ d 0.495720 0.625847
+ r 0.106935
+ d 0.522268 0.599753
+ r 0.106915
+ d 0.547553 0.570199
+ r 0.106933
+ d 0.569604 0.535951
+ r 0.106990
+ d 0.588399 0.497007
+ r 0.107053
+ d 0.606222 0.457218
+ r 0.107076
+ d 0.605565 0.431983
+ r 0.107003
+ d 0.596030 0.477714
+ r 0.106976
+ d 0.590207 0.524973
+ r 0.107035
+ d 0.603324 0.537552
+ r 0.107127
+ d 0.624703 0.514553
+ r 0.107170
+ d 0.624940 0.562699
+ r 0.107252
+ d 0.625833 0.610832
+ r 0.107355
+ d 0.623089 0.656983
+ r 0.107382
+ d 0.641347 0.629751
+ r 0.107315
+ d 0.646258 0.582121
+ r 0.107273
+ d 0.647777 0.540055
+ r 0.107272
+ d 0.637604 0.585766
+ r 0.107374
+ d 0.628826 0.632228
+ r 0.107503
+ d 0.620427 0.678861
+ r 0.107632
+ d 0.612028 0.725494
+ r 0.107762
+ d 0.603629 0.772127
+ r 0.107964
+ d 0.593573 0.818167
+ r 0.107803
+ d 0.600664 0.784909
+ r 0.107621
+ d 0.611126 0.739181
+ r 0.107473
+ d 0.620815 0.693183
+ r 0.107357
+ d 0.629142 0.646568
+ r 0.107246
+ d 0.629798 0.598641
+ r 0.107112
+ d 0.611782 0.572861
+ r 0.107304
+ d 0.637627 0.601544
+ r 0.107535
+ d 0.664347 0.628550
+ r 0.107696
+ d 0.685524 0.632845
+ r 0.107544
+ d 0.677592 0.586715
+ r 0.107398
+ d 0.665785 0.541865
+ r 0.107339
+ d 0.658137 0.533667
+ r 0.107396
+ d 0.658178 0.581910
+ r 0.107490
+ d 0.657815 0.630181
+ r 0.107569
+ d 0.649486 0.675674
+ r 0.107475
+ d 0.650755 0.641654
+ r 0.107412
+ d 0.657007 0.594506
+ r 0.107350
+ d 0.657950 0.546796
+ r 0.107280
+ d 0.651304 0.499629
+ r 0.107214
+ d 0.640101 0.514470
+ r 0.107378
+ d 0.662679 0.547600
+ r 0.107605
+ d 0.688417 0.576858
+ r 0.107869
+ d 0.712434 0.609328
+ r 0.107815
+ d 0.710900 0.587842
+ r 0.107659
+ d 0.697665 0.543732
+ r 0.107521
+ d 0.684362 0.499767
+ r 0.107445
+ d 0.670409 0.456254
+ r 0.107393
+ d 0.657024 0.412397
+ r 0.107438
+ d 0.649885 0.368699
+ r 0.107600
+ d 0.651247 0.320426
+ r 0.107672
+ d 0.640848 0.282640
+ r 0.107446
+ d 0.614652 0.303731
+ r 0.107233
+ d 0.598648 0.345469
+ r 0.107080
+ d 0.577041 0.372584
+ r 0.106989
+ d 0.555666 0.386802
+ r 0.106914
+ d 0.556263 0.429504
+ r 0.106866
+ d 0.525492 0.427890
+
+
+ 0.200000
+ 1.000000
+ 0.106906
+ Mask/Paint
+
+
+
+ 0.000000
+ 0.373183
+ 0.059684
+ 0.000000
+ 0.301508
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.091025
+ 0.000000
+ -0.099382
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ -11
+ 50
+ 0
+ 50
+ 0
+ 1
+ 6.7
+ DSC07033.jpg
+ +26
+ +50
+ -33
+ 0
+ -56
+ -69
+ -35
+ +19
+ -35
+ 0
+ -30
+ -2
+ +39
+ 25
+ 0
+ +1.0
+ 35
+ 0
+ 0
+ 0
+ 0
+ 0
+ 6150
+ +17
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 28
+ 54, 62
+ 176, 164
+ 255, 224
+
+
+
+
+ 0, 0
+ 39, 13
+ 87, 65
+ 123, 124
+ 183, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 14
+ 83, 59
+ 123, 126
+ 172, 185
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 12
+ 84, 58
+ 123, 125
+ 186, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 9.0
+ 0
+ 0
+ Custom
+ -49
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_45_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_45_formatted_canonical.xmp
new file mode 100644
index 0000000..2dacafc
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_45_formatted_canonical.xmp
@@ -0,0 +1,669 @@
+
+
+
+
+ FE 24-240mm F3.5-6.3 OSS
+ 240/10 2400/10 35/10 63/10
+ True
+ 1
+ 134348800
+ +7
+ -11
+ 0
+ Adobe Standard
+ +5
+ 25
+ 50
+ 50
+ +26
+ False
+ 0
+ 0.952836
+ 0
+ 0.0051
+ 0.852836
+ 0.089087
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +0.59
+ 15
+ 50
+ 1866158102
+ 45
+ +44
+ 0
+ True
+ True
+ -82
+ +10
+ -23
+ 0
+ +20
+ +5
+ +20
+ +10
+ 0
+ 0
+ 100
+ 0E0E0483ED048452CE6CC01CB9D7D893
+ 100
+ 0
+ SONY (Sony FE 24-240mm F3.5-6.3 OSS) - RAW.lcp
+ Adobe (Sony FE 24-240mm F3.5-6.3 OSS)
+ LensDefaults
+ 100
+ -12
+ +59
+ 0
+ 0
+ +5
+ 0
+ 0
+ 0
+ 0
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.092338
+
+
+ d 0.550077 0.453913
+ r 0.106822
+ d 0.520538 0.468930
+ r 0.106808
+ d 0.500725 0.505721
+ r 0.106833
+ d 0.496680 0.552137
+ r 0.106876
+ d 0.519958 0.580808
+ r 0.106895
+ d 0.548101 0.566140
+ r 0.106881
+ d 0.560024 0.524490
+ r 0.106884
+ d 0.561107 0.476508
+ r 0.106899
+ d 0.552378 0.434688
+ r 0.106846
+ d 0.523235 0.444702
+ r 0.106816
+ d 0.499427 0.476094
+ r 0.106824
+ d 0.480304 0.514348
+ r 0.106858
+ d 0.475552 0.559611
+ r 0.106897
+ d 0.492413 0.595464
+ r 0.106870
+ d 0.518960 0.575597
+ r 0.106846
+ d 0.536703 0.536319
+ r 0.106861
+ d 0.546815 0.491035
+ r 0.106893
+ d 0.554394 0.444487
+ r 0.106968
+ d 0.555413 0.396484
+ r 0.106973
+ d 0.543178 0.384028
+ r 0.106879
+ d 0.525867 0.424331
+ r 0.106823
+ d 0.511626 0.467138
+ r 0.106810
+ d 0.503710 0.512444
+ r 0.106845
+ d 0.513228 0.558261
+ r 0.106928
+ d 0.540532 0.576265
+ r 0.107022
+ d 0.571590 0.588377
+ r 0.107117
+ d 0.602716 0.600434
+ r 0.107259
+ d 0.632682 0.596823
+ r 0.107191
+ d 0.631266 0.549494
+ r 0.107117
+ d 0.622830 0.503102
+ r 0.107075
+ d 0.611411 0.458268
+ r 0.106992
+ d 0.590011 0.449967
+ r 0.107005
+ d 0.598077 0.496484
+ r 0.107091
+ d 0.613109 0.538498
+ r 0.107226
+ d 0.633399 0.575002
+ r 0.107417
+ d 0.664455 0.571043
+ r 0.107397
+ d 0.666784 0.535788
+ r 0.107252
+ d 0.642236 0.504925
+ r 0.107108
+ d 0.617137 0.474806
+ r 0.106982
+ d 0.588570 0.454908
+ r 0.106895
+ d 0.566615 0.482976
+ r 0.106887
+ d 0.560958 0.529939
+ r 0.106952
+ d 0.570297 0.571515
+ r 0.106964
+ d 0.581924 0.526779
+ r 0.106985
+ d 0.592769 0.481572
+ r 0.107060
+ d 0.599760 0.434648
+ r 0.107143
+ d 0.602691 0.387180
+ r 0.107148
+ d 0.582107 0.353694
+ r 0.107091
+ d 0.551252 0.343667
+ r 0.106978
+ d 0.528428 0.372647
+ r 0.106885
+ d 0.522544 0.419049
+ r 0.106828
+ d 0.526121 0.464519
+ r 0.106892
+ d 0.554594 0.446531
+ r 0.106998
+ d 0.579749 0.417010
+ r 0.107063
+ d 0.578712 0.381745
+ r 0.107047
+ d 0.588228 0.406282
+ r 0.107212
+ d 0.610614 0.372765
+ r 0.107236
+ d 0.595553 0.340617
+ r 0.107125
+ d 0.564782 0.343689
+ r 0.106988
+ d 0.542013 0.376746
+ r 0.106882
+ d 0.525553 0.417782
+ r 0.106948
+ d 0.544006 0.394959
+ r 0.106969
+ d 0.528907 0.376524
+ r 0.106971
+ d 0.497175 0.369564
+ r 0.106902
+ d 0.509024 0.400551
+ r 0.106944
+ d 0.525368 0.384667
+ r 0.106898
+ d 0.508112 0.403409
+ r 0.106845
+ d 0.503437 0.450597
+ r 0.106814
+ d 0.501593 0.498503
+ r 0.106823
+ d 0.516832 0.537627
+ r 0.106858
+ d 0.547423 0.531031
+ r 0.106877
+ d 0.559390 0.488709
+ r 0.106910
+ d 0.557095 0.441228
+ r 0.106929
+ d 0.541816 0.403862
+ r 0.106870
+ d 0.517747 0.421556
+ r 0.106822
+ d 0.508947 0.467700
+ r 0.106815
+ d 0.503279 0.514841
+ r 0.106849
+ d 0.499223 0.562437
+ r 0.106909
+ d 0.485215 0.599547
+ r 0.106897
+ d 0.490295 0.592247
+ r 0.106860
+ d 0.517689 0.567318
+ r 0.106862
+ d 0.544311 0.540665
+ r 0.106884
+ d 0.560416 0.502504
+ r 0.106917
+ d 0.566212 0.455446
+ r 0.106988
+ d 0.566901 0.407415
+ r 0.107085
+ d 0.567243 0.359335
+ r 0.106975
+ d 0.556458 0.395924
+ r 0.106910
+ d 0.552261 0.443356
+ r 0.106865
+ d 0.549546 0.491197
+ r 0.106897
+ d 0.559145 0.534934
+ r 0.106976
+ d 0.577398 0.571906
+ r 0.107068
+ d 0.604027 0.566485
+ r 0.107019
+ d 0.600219 0.518930
+
+
+ 0.200000
+ 1.000000
+ 0.106873
+ Mask/Paint
+
+
+
+
+ 0.092338
+
+
+ d 0.566661 0.538366
+ r 0.106882
+ d 0.541813 0.567195
+ r 0.106929
+ d 0.536683 0.601096
+ r 0.106970
+ d 0.566832 0.588891
+ r 0.106923
+ d 0.568658 0.550795
+ r 0.106863
+ d 0.538016 0.553920
+ r 0.106882
+ d 0.512408 0.581818
+ r 0.106949
+ d 0.490852 0.617374
+ r 0.107024
+ d 0.482470 0.646244
+ r 0.106953
+ d 0.510312 0.622943
+ r 0.106896
+ d 0.530616 0.585900
+ r 0.106886
+ d 0.544947 0.542953
+ r 0.106876
+ d 0.559278 0.500006
+ r 0.106922
+ d 0.567467 0.453660
+ r 0.106886
+ d 0.552312 0.453384
+ r 0.106839
+ d 0.527834 0.483247
+ r 0.106840
+ d 0.507702 0.520587
+ r 0.106845
+ d 0.487778 0.558154
+ r 0.106927
+ d 0.473313 0.601001
+ r 0.106976
+ d 0.490174 0.630790
+ r 0.106980
+ d 0.520269 0.631014
+ r 0.106927
+ d 0.540365 0.594812
+ r 0.106889
+ d 0.551051 0.550057
+ r 0.106868
+ d 0.556893 0.502988
+ r 0.106833
+ d 0.537076 0.498236
+ r 0.106824
+ d 0.514421 0.532173
+ r 0.106862
+ d 0.495516 0.570725
+ r 0.106946
+ d 0.481764 0.613786
+ r 0.106964
+ d 0.495720 0.625847
+ r 0.106935
+ d 0.522268 0.599753
+ r 0.106915
+ d 0.547553 0.570199
+ r 0.106933
+ d 0.569604 0.535951
+ r 0.106990
+ d 0.588399 0.497007
+ r 0.107053
+ d 0.606222 0.457218
+ r 0.107076
+ d 0.605565 0.431983
+ r 0.107003
+ d 0.596030 0.477714
+ r 0.106976
+ d 0.590207 0.524973
+ r 0.107035
+ d 0.603324 0.537552
+ r 0.107127
+ d 0.624703 0.514553
+ r 0.107170
+ d 0.624940 0.562699
+ r 0.107252
+ d 0.625833 0.610832
+ r 0.107355
+ d 0.623089 0.656983
+ r 0.107382
+ d 0.641347 0.629751
+ r 0.107315
+ d 0.646258 0.582121
+ r 0.107273
+ d 0.647777 0.540055
+ r 0.107272
+ d 0.637604 0.585766
+ r 0.107374
+ d 0.628826 0.632228
+ r 0.107503
+ d 0.620427 0.678861
+ r 0.107632
+ d 0.612028 0.725494
+ r 0.107762
+ d 0.603629 0.772127
+ r 0.107964
+ d 0.593573 0.818167
+ r 0.107803
+ d 0.600664 0.784909
+ r 0.107621
+ d 0.611126 0.739181
+ r 0.107473
+ d 0.620815 0.693183
+ r 0.107357
+ d 0.629142 0.646568
+ r 0.107246
+ d 0.629798 0.598641
+ r 0.107112
+ d 0.611782 0.572861
+ r 0.107304
+ d 0.637627 0.601544
+ r 0.107535
+ d 0.664347 0.628550
+ r 0.107696
+ d 0.685524 0.632845
+ r 0.107544
+ d 0.677592 0.586715
+ r 0.107398
+ d 0.665785 0.541865
+ r 0.107339
+ d 0.658137 0.533667
+ r 0.107396
+ d 0.658178 0.581910
+ r 0.107490
+ d 0.657815 0.630181
+ r 0.107569
+ d 0.649486 0.675674
+ r 0.107475
+ d 0.650755 0.641654
+ r 0.107412
+ d 0.657007 0.594506
+ r 0.107350
+ d 0.657950 0.546796
+ r 0.107280
+ d 0.651304 0.499629
+ r 0.107214
+ d 0.640101 0.514470
+ r 0.107378
+ d 0.662679 0.547600
+ r 0.107605
+ d 0.688417 0.576858
+ r 0.107869
+ d 0.712434 0.609328
+ r 0.107815
+ d 0.710900 0.587842
+ r 0.107659
+ d 0.697665 0.543732
+ r 0.107521
+ d 0.684362 0.499767
+ r 0.107445
+ d 0.670409 0.456254
+ r 0.107393
+ d 0.657024 0.412397
+ r 0.107438
+ d 0.649885 0.368699
+ r 0.107600
+ d 0.651247 0.320426
+ r 0.107672
+ d 0.640848 0.282640
+ r 0.107446
+ d 0.614652 0.303731
+ r 0.107233
+ d 0.598648 0.345469
+ r 0.107080
+ d 0.577041 0.372584
+ r 0.106989
+ d 0.555666 0.386802
+ r 0.106914
+ d 0.556263 0.429504
+ r 0.106866
+ d 0.525492 0.427890
+
+
+ 0.200000
+ 1.000000
+ 0.106906
+ Mask/Paint
+
+
+
+
+ 0.000000
+ 0.373183
+ 0.059684
+ 0.000000
+ 0.301508
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.091025
+ 0.000000
+ -0.099382
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ -11
+ 50
+ 0
+ 50
+ 0
+ 1
+ 6.7
+ DSC07033.jpg
+ +26
+ +50
+ -33
+ 0
+ -56
+ -69
+ -35
+ +19
+ -35
+ 0
+ -30
+ -2
+ +39
+ 25
+ 0
+ +1.0
+ 35
+ 0
+ 0
+ 0
+ 0
+ 0
+ 6150
+ +17
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 28
+ 54, 62
+ 176, 164
+ 255, 224
+
+
+
+
+ 0, 0
+ 39, 13
+ 87, 65
+ 123, 124
+ 183, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 14
+ 83, 59
+ 123, 126
+ 172, 185
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 12
+ 84, 58
+ 123, 125
+ 186, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 9.0
+ 0
+ 0
+ Custom
+ -49
+ image/jpeg
+ 2017-07-17T18:21:14
+ 2017-07-17T18:21:14
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T22:04:13-03:00
+ 2017-07-17T22:04:13-03:00
+ 2
+
+
+ xmp.did:b593b3ed-ff2f-4e8e-95d5-7cc5988c01c5
+ xmp.iid:b593b3ed-ff2f-4e8e-95d5-7cc5988c01c5
+ 6BE69D49EC5B5C820D2EEE864415D602
+
+
+ xmp.did:1a499823-4165-4149-9355-5038b48eda98
+
+
+
+
+ converted
+ from image/x-sony-arw to image/dng
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:b593b3ed-ff2f-4e8e-95d5-7cc5988c01c5
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T20:37-03:00
+
+
+
+
+ derived
+ converted from image/dng to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:1a499823-4165-4149-9355-5038b48eda98
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T22:04:13-03:00
+
+
+
+
+ saved
+ /metadata
+ xmp.iid:e5101770-6dc9-4c2d-93ba-7e0dddb726be
+ Adobe Photoshop Lightroom 6.0 (Macintosh)
+ 2017-07-17T22:04:13-03:00
+
+
+
+
+ xmp.iid:e5101770-6dc9-4c2d-93ba-7e0dddb726be
+ 6BE69D49EC5B5C820D2EEE864415D602
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_45_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_45_formatted_compact.xmp
new file mode 100644
index 0000000..09e00ef
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_45_formatted_compact.xmp
@@ -0,0 +1,651 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.550077 0.453913
+ r 0.106822
+ d 0.520538 0.468930
+ r 0.106808
+ d 0.500725 0.505721
+ r 0.106833
+ d 0.496680 0.552137
+ r 0.106876
+ d 0.519958 0.580808
+ r 0.106895
+ d 0.548101 0.566140
+ r 0.106881
+ d 0.560024 0.524490
+ r 0.106884
+ d 0.561107 0.476508
+ r 0.106899
+ d 0.552378 0.434688
+ r 0.106846
+ d 0.523235 0.444702
+ r 0.106816
+ d 0.499427 0.476094
+ r 0.106824
+ d 0.480304 0.514348
+ r 0.106858
+ d 0.475552 0.559611
+ r 0.106897
+ d 0.492413 0.595464
+ r 0.106870
+ d 0.518960 0.575597
+ r 0.106846
+ d 0.536703 0.536319
+ r 0.106861
+ d 0.546815 0.491035
+ r 0.106893
+ d 0.554394 0.444487
+ r 0.106968
+ d 0.555413 0.396484
+ r 0.106973
+ d 0.543178 0.384028
+ r 0.106879
+ d 0.525867 0.424331
+ r 0.106823
+ d 0.511626 0.467138
+ r 0.106810
+ d 0.503710 0.512444
+ r 0.106845
+ d 0.513228 0.558261
+ r 0.106928
+ d 0.540532 0.576265
+ r 0.107022
+ d 0.571590 0.588377
+ r 0.107117
+ d 0.602716 0.600434
+ r 0.107259
+ d 0.632682 0.596823
+ r 0.107191
+ d 0.631266 0.549494
+ r 0.107117
+ d 0.622830 0.503102
+ r 0.107075
+ d 0.611411 0.458268
+ r 0.106992
+ d 0.590011 0.449967
+ r 0.107005
+ d 0.598077 0.496484
+ r 0.107091
+ d 0.613109 0.538498
+ r 0.107226
+ d 0.633399 0.575002
+ r 0.107417
+ d 0.664455 0.571043
+ r 0.107397
+ d 0.666784 0.535788
+ r 0.107252
+ d 0.642236 0.504925
+ r 0.107108
+ d 0.617137 0.474806
+ r 0.106982
+ d 0.588570 0.454908
+ r 0.106895
+ d 0.566615 0.482976
+ r 0.106887
+ d 0.560958 0.529939
+ r 0.106952
+ d 0.570297 0.571515
+ r 0.106964
+ d 0.581924 0.526779
+ r 0.106985
+ d 0.592769 0.481572
+ r 0.107060
+ d 0.599760 0.434648
+ r 0.107143
+ d 0.602691 0.387180
+ r 0.107148
+ d 0.582107 0.353694
+ r 0.107091
+ d 0.551252 0.343667
+ r 0.106978
+ d 0.528428 0.372647
+ r 0.106885
+ d 0.522544 0.419049
+ r 0.106828
+ d 0.526121 0.464519
+ r 0.106892
+ d 0.554594 0.446531
+ r 0.106998
+ d 0.579749 0.417010
+ r 0.107063
+ d 0.578712 0.381745
+ r 0.107047
+ d 0.588228 0.406282
+ r 0.107212
+ d 0.610614 0.372765
+ r 0.107236
+ d 0.595553 0.340617
+ r 0.107125
+ d 0.564782 0.343689
+ r 0.106988
+ d 0.542013 0.376746
+ r 0.106882
+ d 0.525553 0.417782
+ r 0.106948
+ d 0.544006 0.394959
+ r 0.106969
+ d 0.528907 0.376524
+ r 0.106971
+ d 0.497175 0.369564
+ r 0.106902
+ d 0.509024 0.400551
+ r 0.106944
+ d 0.525368 0.384667
+ r 0.106898
+ d 0.508112 0.403409
+ r 0.106845
+ d 0.503437 0.450597
+ r 0.106814
+ d 0.501593 0.498503
+ r 0.106823
+ d 0.516832 0.537627
+ r 0.106858
+ d 0.547423 0.531031
+ r 0.106877
+ d 0.559390 0.488709
+ r 0.106910
+ d 0.557095 0.441228
+ r 0.106929
+ d 0.541816 0.403862
+ r 0.106870
+ d 0.517747 0.421556
+ r 0.106822
+ d 0.508947 0.467700
+ r 0.106815
+ d 0.503279 0.514841
+ r 0.106849
+ d 0.499223 0.562437
+ r 0.106909
+ d 0.485215 0.599547
+ r 0.106897
+ d 0.490295 0.592247
+ r 0.106860
+ d 0.517689 0.567318
+ r 0.106862
+ d 0.544311 0.540665
+ r 0.106884
+ d 0.560416 0.502504
+ r 0.106917
+ d 0.566212 0.455446
+ r 0.106988
+ d 0.566901 0.407415
+ r 0.107085
+ d 0.567243 0.359335
+ r 0.106975
+ d 0.556458 0.395924
+ r 0.106910
+ d 0.552261 0.443356
+ r 0.106865
+ d 0.549546 0.491197
+ r 0.106897
+ d 0.559145 0.534934
+ r 0.106976
+ d 0.577398 0.571906
+ r 0.107068
+ d 0.604027 0.566485
+ r 0.107019
+ d 0.600219 0.518930
+
+
+
+
+
+
+
+
+ d 0.566661 0.538366
+ r 0.106882
+ d 0.541813 0.567195
+ r 0.106929
+ d 0.536683 0.601096
+ r 0.106970
+ d 0.566832 0.588891
+ r 0.106923
+ d 0.568658 0.550795
+ r 0.106863
+ d 0.538016 0.553920
+ r 0.106882
+ d 0.512408 0.581818
+ r 0.106949
+ d 0.490852 0.617374
+ r 0.107024
+ d 0.482470 0.646244
+ r 0.106953
+ d 0.510312 0.622943
+ r 0.106896
+ d 0.530616 0.585900
+ r 0.106886
+ d 0.544947 0.542953
+ r 0.106876
+ d 0.559278 0.500006
+ r 0.106922
+ d 0.567467 0.453660
+ r 0.106886
+ d 0.552312 0.453384
+ r 0.106839
+ d 0.527834 0.483247
+ r 0.106840
+ d 0.507702 0.520587
+ r 0.106845
+ d 0.487778 0.558154
+ r 0.106927
+ d 0.473313 0.601001
+ r 0.106976
+ d 0.490174 0.630790
+ r 0.106980
+ d 0.520269 0.631014
+ r 0.106927
+ d 0.540365 0.594812
+ r 0.106889
+ d 0.551051 0.550057
+ r 0.106868
+ d 0.556893 0.502988
+ r 0.106833
+ d 0.537076 0.498236
+ r 0.106824
+ d 0.514421 0.532173
+ r 0.106862
+ d 0.495516 0.570725
+ r 0.106946
+ d 0.481764 0.613786
+ r 0.106964
+ d 0.495720 0.625847
+ r 0.106935
+ d 0.522268 0.599753
+ r 0.106915
+ d 0.547553 0.570199
+ r 0.106933
+ d 0.569604 0.535951
+ r 0.106990
+ d 0.588399 0.497007
+ r 0.107053
+ d 0.606222 0.457218
+ r 0.107076
+ d 0.605565 0.431983
+ r 0.107003
+ d 0.596030 0.477714
+ r 0.106976
+ d 0.590207 0.524973
+ r 0.107035
+ d 0.603324 0.537552
+ r 0.107127
+ d 0.624703 0.514553
+ r 0.107170
+ d 0.624940 0.562699
+ r 0.107252
+ d 0.625833 0.610832
+ r 0.107355
+ d 0.623089 0.656983
+ r 0.107382
+ d 0.641347 0.629751
+ r 0.107315
+ d 0.646258 0.582121
+ r 0.107273
+ d 0.647777 0.540055
+ r 0.107272
+ d 0.637604 0.585766
+ r 0.107374
+ d 0.628826 0.632228
+ r 0.107503
+ d 0.620427 0.678861
+ r 0.107632
+ d 0.612028 0.725494
+ r 0.107762
+ d 0.603629 0.772127
+ r 0.107964
+ d 0.593573 0.818167
+ r 0.107803
+ d 0.600664 0.784909
+ r 0.107621
+ d 0.611126 0.739181
+ r 0.107473
+ d 0.620815 0.693183
+ r 0.107357
+ d 0.629142 0.646568
+ r 0.107246
+ d 0.629798 0.598641
+ r 0.107112
+ d 0.611782 0.572861
+ r 0.107304
+ d 0.637627 0.601544
+ r 0.107535
+ d 0.664347 0.628550
+ r 0.107696
+ d 0.685524 0.632845
+ r 0.107544
+ d 0.677592 0.586715
+ r 0.107398
+ d 0.665785 0.541865
+ r 0.107339
+ d 0.658137 0.533667
+ r 0.107396
+ d 0.658178 0.581910
+ r 0.107490
+ d 0.657815 0.630181
+ r 0.107569
+ d 0.649486 0.675674
+ r 0.107475
+ d 0.650755 0.641654
+ r 0.107412
+ d 0.657007 0.594506
+ r 0.107350
+ d 0.657950 0.546796
+ r 0.107280
+ d 0.651304 0.499629
+ r 0.107214
+ d 0.640101 0.514470
+ r 0.107378
+ d 0.662679 0.547600
+ r 0.107605
+ d 0.688417 0.576858
+ r 0.107869
+ d 0.712434 0.609328
+ r 0.107815
+ d 0.710900 0.587842
+ r 0.107659
+ d 0.697665 0.543732
+ r 0.107521
+ d 0.684362 0.499767
+ r 0.107445
+ d 0.670409 0.456254
+ r 0.107393
+ d 0.657024 0.412397
+ r 0.107438
+ d 0.649885 0.368699
+ r 0.107600
+ d 0.651247 0.320426
+ r 0.107672
+ d 0.640848 0.282640
+ r 0.107446
+ d 0.614652 0.303731
+ r 0.107233
+ d 0.598648 0.345469
+ r 0.107080
+ d 0.577041 0.372584
+ r 0.106989
+ d 0.555666 0.386802
+ r 0.106914
+ d 0.556263 0.429504
+ r 0.106866
+ d 0.525492 0.427890
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 28
+ 54, 62
+ 176, 164
+ 255, 224
+
+
+
+
+ 0, 0
+ 39, 13
+ 87, 65
+ 123, 124
+ 183, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 14
+ 83, 59
+ 123, 126
+ 172, 185
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 12
+ 84, 58
+ 123, 125
+ 186, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_46.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_46.xmp
new file mode 100644
index 0000000..55061c5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_46.xmp
@@ -0,0 +1,275 @@
+
+
+
+
+
+ 1.1.8
+ 0/1
+ 0
+ 100mm
+ 220
+ 100/1 100/1 0/0 0/0
+ 0000000000
+ 022123002072
+
+
+
+
+
+ Jonathan Cooper
+
+
+ image/jpeg
+
+
+ 2014
+
+
+
+
+
+ 100mm
+
+
+
+ 2018-10-20T15:19:58.62
+
+
+
+ 2018-10-20T15:19:58.62
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-03-04T15:08:58-03:30
+ 2019-03-04T15:08:58-03:30
+ 0
+
+
+
+
+ 0D1AECB577F991DDB109A54A11A712CC
+ 0D1AECB577F991DDB109A54A11A712CC
+
+ xmp.did:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-03-04T15:08:58-03:30
+
+
+
+ xmp.iid:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+ 0D1AECB577F991DDB109A54A11A712CC
+
+
+
+ True
+ 0
+ 0
+ -50
+ 0
+ Adobe Standard
+ 98BA1AFA1155D0472068BB57D3655975
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 1
+ 0
+ 0.157865
+ 0.960293
+ 0.197572
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.90
+ 0
+ +16
+ 0
+ True
+ True
+ -100
+ +17
+ 0
+ +58
+ 0
+ +8
+ 0
+ +7
+ -6
+ 0
+ 0
+ LensDefaults
+
+
+
+ +8
+ 0
+ +16
+ 0
+ 0
+ 0
+ 0
+ +4
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ IMG_0081.CR2
+ +9
+ 0
+ -11
+ 0
+ 0
+ -32
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5750
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 26
+ 77, 54
+ 127, 125
+ 179, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ +20
+ 0
+ As Shot
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_46_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_46_formatted_canonical.xmp
new file mode 100644
index 0000000..47c00c9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_46_formatted_canonical.xmp
@@ -0,0 +1,240 @@
+
+
+
+
+ 1.1.8
+ 0/1
+ 0
+ 100mm
+ 220
+ 100/1 100/1 0/0 0/0
+ 0000000000
+ 022123002072
+ True
+ 0
+ 0
+ -50
+ 0
+ Adobe Standard
+ 98BA1AFA1155D0472068BB57D3655975
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 1
+ 0
+ 0.157865
+ 0.960293
+ 0.197572
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.90
+ 0
+ +16
+ 0
+ True
+ True
+ -100
+ +17
+ 0
+ +58
+ 0
+ +8
+ 0
+ +7
+ -6
+ 0
+ 0
+ LensDefaults
+
+
+
+
+
+ +8
+ 0
+ +16
+ 0
+ 0
+ 0
+ 0
+ +4
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ IMG_0081.CR2
+ +9
+ 0
+ -11
+ 0
+ 0
+ -32
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5750
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 26
+ 77, 54
+ 127, 125
+ 179, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.0
+ +20
+ 0
+ As Shot
+ 0
+
+
+ Jonathan Cooper
+
+
+ image/jpeg
+
+
+ 2014
+
+
+ 100mm
+ 2018-10-20T15:19:58.62
+ 2018-10-20T15:19:58.62
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-03-04T15:08:58-03:30
+ 2019-03-04T15:08:58-03:30
+ 0
+
+
+ 0D1AECB577F991DDB109A54A11A712CC
+ 0D1AECB577F991DDB109A54A11A712CC
+
+
+ xmp.did:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+ Adobe Photoshop Lightroom Classic 8.0 (Macintosh)
+ 2019-03-04T15:08:58-03:30
+
+
+
+
+ xmp.iid:56c6df46-c450-4a4f-a3fa-953b81fc65bf
+ 0D1AECB577F991DDB109A54A11A712CC
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_46_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_46_formatted_compact.xmp
new file mode 100644
index 0000000..ade0490
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_46_formatted_compact.xmp
@@ -0,0 +1,228 @@
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 26
+ 77, 54
+ 127, 125
+ 179, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ Jonathan Cooper
+
+
+
+
+ 2014
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_47.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_47.xmp
new file mode 100644
index 0000000..8e06893
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_47.xmp
@@ -0,0 +1,609 @@
+
+
+
+
+
+ True
+ 0/256
+ True
+ OLYMPUS M.40-150mm F2.8
+ 40/1 150/1 28/10 28/10
+ ABV25833600
+ BHEA65641
+
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+ image/jpeg
+
+
+
+ 2018-07-13T19:38:07
+
+
+
+ 2018-07-13T19:38:07
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2018-07-16T17:40:38+03:00
+ 2018-07-16T17:40:38+03:00
+ 2
+
+
+
+
+ 19C95676B875B72CBA76DB2803846421
+ 19C95676B875B72CBA76DB2803846421
+
+ xmp.did:b18ed21a-e5ef-ed4b-b9e3-36eb3191b27c
+
+
+
+ derived
+ converted from image/x-olympus-raw to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:b18ed21a-e5ef-ed4b-b9e3-36eb3191b27c
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2018-07-16T17:40:38+03:00
+
+
+
+ xmp.iid:b18ed21a-e5ef-ed4b-b9e3-36eb3191b27c
+ 19C95676B875B72CBA76DB2803846421
+
+
+
+ True
+ 1
+ 134348800
+ +25
+ -15
+ +25
+ 160S - O
+ 0
+ 25
+ 50
+ 50
+ +38
+ False
+ 5.22804
+ 0.969844
+ 0
+ 0.185002
+ 0.781537
+ 0.01425
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ -10
+ -10
+ True
+ True
+ -25
+ 0
+ +5
+ +18
+ +15
+ +2
+ +30
+ +8
+ 0
+ 0
+ 1
+ LensDefaults
+ 0
+ -10
+ -15
+ -25
+ 0
+ -10
+ -20
+ 0
+ 0
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+
+
+ d 0.404990 0.560042
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+ 0.000000
+ 0.000000
+ 0.100000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.087500
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.400000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+
+
+ d 0.376715 0.569064
+ d 0.377427 0.558694
+ d 0.378138 0.548324
+ d 0.380399 0.538425
+ d 0.383085 0.528775
+ d 0.386593 0.519944
+ d 0.385504 0.516475
+ d 0.378134 0.517808
+ d 0.373985 0.525869
+ d 0.374559 0.536246
+ d 0.374599 0.546625
+ d 0.375754 0.556874
+ d 0.377181 0.566903
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.429040 0.654077
+ d 0.431665 0.659028
+ d 0.437549 0.659064
+ d 0.443434 0.659099
+ d 0.448315 0.657662
+ d 0.449082 0.652453
+ d 0.453900 0.655908
+ d 0.458937 0.659909
+ d 0.464198 0.663141
+ d 0.469963 0.663990
+ d 0.475610 0.666141
+ d 0.481349 0.667865
+ d 0.486797 0.670342
+ d 0.488500 0.672110
+ d 0.482756 0.672949
+ d 0.476986 0.671575
+ d 0.471209 0.670086
+ d 0.465406 0.668817
+ d 0.463083 0.668818
+ d 0.468608 0.671393
+ d 0.474300 0.673380
+ d 0.479900 0.675769
+ d 0.482506 0.681464
+ d 0.487579 0.684993
+ d 0.493140 0.687528
+ d 0.498897 0.689151
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.429825 0.618925
+ d 0.432046 0.615320
+ d 0.436548 0.620219
+ d 0.434887 0.613064
+ d 0.431890 0.606620
+ d 0.428821 0.605788
+ d 0.424807 0.609080
+ d 0.422208 0.615796
+ d 0.417310 0.619566
+ d 0.412318 0.623297
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.038750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+
+
+ d 0.505018 0.649247
+ d 0.512106 0.645025
+ d 0.519853 0.643702
+ d 0.527454 0.641755
+ d 0.534539 0.637449
+ d 0.541338 0.632907
+ d 0.546924 0.626947
+ d 0.549725 0.632085
+ d 0.542671 0.636047
+ d 0.534953 0.637133
+ d 0.533305 0.641372
+ d 0.539342 0.646463
+ d 0.546993 0.648403
+ d 0.554148 0.650706
+ d 0.550168 0.646192
+ d 0.542418 0.644998
+ d 0.535081 0.641677
+ d 0.528558 0.635991
+ d 0.520798 0.635989
+ d 0.513424 0.633180
+ d 0.506165 0.629866
+ d 0.498387 0.628917
+ d 0.490697 0.628553
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.481896 0.612806
+ d 0.487198 0.605184
+ d 0.492640 0.597716
+ d 0.498165 0.590357
+ d 0.503923 0.583346
+ d 0.509922 0.576991
+ d 0.517511 0.574602
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.536332 0.566304
+ d 0.544053 0.564737
+ d 0.551812 0.563574
+ d 0.559609 0.563040
+ d 0.567396 0.563587
+ d 0.575174 0.564536
+ d 0.582669 0.567305
+ d 0.590267 0.569631
+ d 0.597125 0.574205
+ d 0.603653 0.579881
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.600859 0.586107
+ d 0.607397 0.591796
+ d 0.613840 0.597681
+ d 0.619843 0.604318
+ d 0.625527 0.611457
+ d 0.630492 0.619298
+ d 0.635482 0.627244
+ d 0.640688 0.634987
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.633463 0.656241
+ d 0.625851 0.653977
+ d 0.626957 0.648804
+ d 0.619501 0.646091
+ d 0.611751 0.644916
+ d 0.603966 0.644099
+ d 0.596167 0.643534
+ d 0.602079 0.640163
+ d 0.608896 0.637589
+ d 0.601158 0.638997
+ d 0.593407 0.640210
+ d 0.595007 0.638676
+ d 0.599933 0.635891
+ d 0.592344 0.638348
+ d 0.584696 0.640460
+ d 0.577605 0.642195
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.406216 0.607369
+ d 0.411749 0.605005
+ d 0.417231 0.602150
+ d 0.421804 0.597585
+ d 0.425376 0.591438
+ d 0.428118 0.584532
+ d 0.428187 0.586151
+ d 0.429801 0.584587
+ d 0.433960 0.579374
+ d 0.437241 0.572974
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.362875 0.604792
+ d 0.359960 0.598251
+ d 0.356368 0.592537
+ d 0.354109 0.585378
+ d 0.356147 0.589672
+ d 0.359237 0.596344
+ d 0.362436 0.602895
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.062500
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 60
+ 14
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ -24
+ 50
+ 0
+ 40
+ -10
+ 1
+ 6.7
+ +10
+ 0
+ +5
+ -15
+ 0
+ -30
+ -20
+ -5
+ -10
+ -10
+ 0
+ 0
+ +15
+ 6
+ 68
+ +1.2
+ 58
+ 0
+ 0
+ 0
+ 0
+ 0
+ 7400
+ +30
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 25
+ 54, 60
+ 111, 115
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 23
+ 119, 124
+ 181, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 24
+ 120, 118
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 43, 25
+ 120, 120
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 10.1
+ 0
+ 0
+ Auto
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_47_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_47_formatted_canonical.xmp
new file mode 100644
index 0000000..8f184df
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_47_formatted_canonical.xmp
@@ -0,0 +1,603 @@
+
+
+
+
+ True
+ 0/256
+ True
+ OLYMPUS M.40-150mm F2.8
+ 40/1 150/1 28/10 28/10
+ ABV25833600
+ BHEA65641
+ True
+ 1
+ 134348800
+ +25
+ -15
+ +25
+ 160S - O
+ 0
+ 25
+ 50
+ 50
+ +38
+ False
+ 5.22804
+ 0.969844
+ 0
+ 0.185002
+ 0.781537
+ 0.01425
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ -10
+ -10
+ True
+ True
+ -25
+ 0
+ +5
+ +18
+ +15
+ +2
+ +30
+ +8
+ 0
+ 0
+ 1
+ LensDefaults
+ 0
+ -10
+ -15
+ -25
+ 0
+ -10
+ -20
+ 0
+ 0
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+
+
+ d 0.404990 0.560042
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.100000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.087500
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.400000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+
+
+ d 0.376715 0.569064
+ d 0.377427 0.558694
+ d 0.378138 0.548324
+ d 0.380399 0.538425
+ d 0.383085 0.528775
+ d 0.386593 0.519944
+ d 0.385504 0.516475
+ d 0.378134 0.517808
+ d 0.373985 0.525869
+ d 0.374559 0.536246
+ d 0.374599 0.546625
+ d 0.375754 0.556874
+ d 0.377181 0.566903
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.429040 0.654077
+ d 0.431665 0.659028
+ d 0.437549 0.659064
+ d 0.443434 0.659099
+ d 0.448315 0.657662
+ d 0.449082 0.652453
+ d 0.453900 0.655908
+ d 0.458937 0.659909
+ d 0.464198 0.663141
+ d 0.469963 0.663990
+ d 0.475610 0.666141
+ d 0.481349 0.667865
+ d 0.486797 0.670342
+ d 0.488500 0.672110
+ d 0.482756 0.672949
+ d 0.476986 0.671575
+ d 0.471209 0.670086
+ d 0.465406 0.668817
+ d 0.463083 0.668818
+ d 0.468608 0.671393
+ d 0.474300 0.673380
+ d 0.479900 0.675769
+ d 0.482506 0.681464
+ d 0.487579 0.684993
+ d 0.493140 0.687528
+ d 0.498897 0.689151
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.429825 0.618925
+ d 0.432046 0.615320
+ d 0.436548 0.620219
+ d 0.434887 0.613064
+ d 0.431890 0.606620
+ d 0.428821 0.605788
+ d 0.424807 0.609080
+ d 0.422208 0.615796
+ d 0.417310 0.619566
+ d 0.412318 0.623297
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ -0.038750
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+
+
+ d 0.505018 0.649247
+ d 0.512106 0.645025
+ d 0.519853 0.643702
+ d 0.527454 0.641755
+ d 0.534539 0.637449
+ d 0.541338 0.632907
+ d 0.546924 0.626947
+ d 0.549725 0.632085
+ d 0.542671 0.636047
+ d 0.534953 0.637133
+ d 0.533305 0.641372
+ d 0.539342 0.646463
+ d 0.546993 0.648403
+ d 0.554148 0.650706
+ d 0.550168 0.646192
+ d 0.542418 0.644998
+ d 0.535081 0.641677
+ d 0.528558 0.635991
+ d 0.520798 0.635989
+ d 0.513424 0.633180
+ d 0.506165 0.629866
+ d 0.498387 0.628917
+ d 0.490697 0.628553
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.481896 0.612806
+ d 0.487198 0.605184
+ d 0.492640 0.597716
+ d 0.498165 0.590357
+ d 0.503923 0.583346
+ d 0.509922 0.576991
+ d 0.517511 0.574602
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.536332 0.566304
+ d 0.544053 0.564737
+ d 0.551812 0.563574
+ d 0.559609 0.563040
+ d 0.567396 0.563587
+ d 0.575174 0.564536
+ d 0.582669 0.567305
+ d 0.590267 0.569631
+ d 0.597125 0.574205
+ d 0.603653 0.579881
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.600859 0.586107
+ d 0.607397 0.591796
+ d 0.613840 0.597681
+ d 0.619843 0.604318
+ d 0.625527 0.611457
+ d 0.630492 0.619298
+ d 0.635482 0.627244
+ d 0.640688 0.634987
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.633463 0.656241
+ d 0.625851 0.653977
+ d 0.626957 0.648804
+ d 0.619501 0.646091
+ d 0.611751 0.644916
+ d 0.603966 0.644099
+ d 0.596167 0.643534
+ d 0.602079 0.640163
+ d 0.608896 0.637589
+ d 0.601158 0.638997
+ d 0.593407 0.640210
+ d 0.595007 0.638676
+ d 0.599933 0.635891
+ d 0.592344 0.638348
+ d 0.584696 0.640460
+ d 0.577605 0.642195
+
+
+ 0.494000
+ 1.000000
+ 0.026034
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.406216 0.607369
+ d 0.411749 0.605005
+ d 0.417231 0.602150
+ d 0.421804 0.597585
+ d 0.425376 0.591438
+ d 0.428118 0.584532
+ d 0.428187 0.586151
+ d 0.429801 0.584587
+ d 0.433960 0.579374
+ d 0.437241 0.572974
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.362875 0.604792
+ d 0.359960 0.598251
+ d 0.356368 0.592537
+ d 0.354109 0.585378
+ d 0.356147 0.589672
+ d 0.359237 0.596344
+ d 0.362436 0.602895
+
+
+ 0.494000
+ 1.000000
+ 0.019615
+ Mask/Paint
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.062500
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 60
+ 14
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ -24
+ 50
+ 0
+ 40
+ -10
+ 1
+ 6.7
+ +10
+ 0
+ +5
+ -15
+ 0
+ -30
+ -20
+ -5
+ -10
+ -10
+ 0
+ 0
+ +15
+ 6
+ 68
+ +1.2
+ 58
+ 0
+ 0
+ 0
+ 0
+ 0
+ 7400
+ +30
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 25
+ 54, 60
+ 111, 115
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 23
+ 119, 124
+ 181, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 24
+ 120, 118
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 43, 25
+ 120, 120
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 10.1
+ 0
+ 0
+ Auto
+ 0
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+ image/jpeg
+ 2018-07-13T19:38:07
+ 2018-07-13T19:38:07
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2018-07-16T17:40:38+03:00
+ 2018-07-16T17:40:38+03:00
+ 2
+
+
+ 19C95676B875B72CBA76DB2803846421
+ 19C95676B875B72CBA76DB2803846421
+
+
+ xmp.did:b18ed21a-e5ef-ed4b-b9e3-36eb3191b27c
+
+
+
+
+ derived
+ converted from image/x-olympus-raw to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:b18ed21a-e5ef-ed4b-b9e3-36eb3191b27c
+ Adobe Photoshop Lightroom 6.14 (Windows)
+ 2018-07-16T17:40:38+03:00
+
+
+
+
+ xmp.iid:b18ed21a-e5ef-ed4b-b9e3-36eb3191b27c
+ 19C95676B875B72CBA76DB2803846421
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_47_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_47_formatted_compact.xmp
new file mode 100644
index 0000000..0d90f83
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_47_formatted_compact.xmp
@@ -0,0 +1,594 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.404990 0.560042
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.376715 0.569064
+ d 0.377427 0.558694
+ d 0.378138 0.548324
+ d 0.380399 0.538425
+ d 0.383085 0.528775
+ d 0.386593 0.519944
+ d 0.385504 0.516475
+ d 0.378134 0.517808
+ d 0.373985 0.525869
+ d 0.374559 0.536246
+ d 0.374599 0.546625
+ d 0.375754 0.556874
+ d 0.377181 0.566903
+
+
+
+
+
+
+
+
+ d 0.429040 0.654077
+ d 0.431665 0.659028
+ d 0.437549 0.659064
+ d 0.443434 0.659099
+ d 0.448315 0.657662
+ d 0.449082 0.652453
+ d 0.453900 0.655908
+ d 0.458937 0.659909
+ d 0.464198 0.663141
+ d 0.469963 0.663990
+ d 0.475610 0.666141
+ d 0.481349 0.667865
+ d 0.486797 0.670342
+ d 0.488500 0.672110
+ d 0.482756 0.672949
+ d 0.476986 0.671575
+ d 0.471209 0.670086
+ d 0.465406 0.668817
+ d 0.463083 0.668818
+ d 0.468608 0.671393
+ d 0.474300 0.673380
+ d 0.479900 0.675769
+ d 0.482506 0.681464
+ d 0.487579 0.684993
+ d 0.493140 0.687528
+ d 0.498897 0.689151
+
+
+
+
+
+
+
+
+ d 0.429825 0.618925
+ d 0.432046 0.615320
+ d 0.436548 0.620219
+ d 0.434887 0.613064
+ d 0.431890 0.606620
+ d 0.428821 0.605788
+ d 0.424807 0.609080
+ d 0.422208 0.615796
+ d 0.417310 0.619566
+ d 0.412318 0.623297
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.505018 0.649247
+ d 0.512106 0.645025
+ d 0.519853 0.643702
+ d 0.527454 0.641755
+ d 0.534539 0.637449
+ d 0.541338 0.632907
+ d 0.546924 0.626947
+ d 0.549725 0.632085
+ d 0.542671 0.636047
+ d 0.534953 0.637133
+ d 0.533305 0.641372
+ d 0.539342 0.646463
+ d 0.546993 0.648403
+ d 0.554148 0.650706
+ d 0.550168 0.646192
+ d 0.542418 0.644998
+ d 0.535081 0.641677
+ d 0.528558 0.635991
+ d 0.520798 0.635989
+ d 0.513424 0.633180
+ d 0.506165 0.629866
+ d 0.498387 0.628917
+ d 0.490697 0.628553
+
+
+
+
+
+
+
+
+ d 0.481896 0.612806
+ d 0.487198 0.605184
+ d 0.492640 0.597716
+ d 0.498165 0.590357
+ d 0.503923 0.583346
+ d 0.509922 0.576991
+ d 0.517511 0.574602
+
+
+
+
+
+
+
+
+ d 0.536332 0.566304
+ d 0.544053 0.564737
+ d 0.551812 0.563574
+ d 0.559609 0.563040
+ d 0.567396 0.563587
+ d 0.575174 0.564536
+ d 0.582669 0.567305
+ d 0.590267 0.569631
+ d 0.597125 0.574205
+ d 0.603653 0.579881
+
+
+
+
+
+
+
+
+ d 0.600859 0.586107
+ d 0.607397 0.591796
+ d 0.613840 0.597681
+ d 0.619843 0.604318
+ d 0.625527 0.611457
+ d 0.630492 0.619298
+ d 0.635482 0.627244
+ d 0.640688 0.634987
+
+
+
+
+
+
+
+
+ d 0.633463 0.656241
+ d 0.625851 0.653977
+ d 0.626957 0.648804
+ d 0.619501 0.646091
+ d 0.611751 0.644916
+ d 0.603966 0.644099
+ d 0.596167 0.643534
+ d 0.602079 0.640163
+ d 0.608896 0.637589
+ d 0.601158 0.638997
+ d 0.593407 0.640210
+ d 0.595007 0.638676
+ d 0.599933 0.635891
+ d 0.592344 0.638348
+ d 0.584696 0.640460
+ d 0.577605 0.642195
+
+
+
+
+
+
+
+
+ d 0.406216 0.607369
+ d 0.411749 0.605005
+ d 0.417231 0.602150
+ d 0.421804 0.597585
+ d 0.425376 0.591438
+ d 0.428118 0.584532
+ d 0.428187 0.586151
+ d 0.429801 0.584587
+ d 0.433960 0.579374
+ d 0.437241 0.572974
+
+
+
+
+
+
+
+
+ d 0.362875 0.604792
+ d 0.359960 0.598251
+ d 0.356368 0.592537
+ d 0.354109 0.585378
+ d 0.356147 0.589672
+ d 0.359237 0.596344
+ d 0.362436 0.602895
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 25
+ 54, 60
+ 111, 115
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 23
+ 119, 124
+ 181, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 41, 24
+ 120, 118
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 43, 25
+ 120, 120
+ 185, 195
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ OLYMPUS DIGITAL CAMERA
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_48.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_48.xmp
new file mode 100644
index 0000000..05f0e95
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_48.xmp
@@ -0,0 +1,115 @@
+
+
+
+
+
+ 400
+
+
+
+
+ This is the description
+
+
+
+
+ animal
+ bird
+
+
+
+
+ sample title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Eye Left
+ Eye Right
+ Nothing
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_48_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_48_formatted_canonical.xmp
new file mode 100644
index 0000000..69504e4
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_48_formatted_canonical.xmp
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+ Eye Left
+ Eye Right
+ Nothing
+
+
+ 2016-02-21T16:23:45.420
+
+ 2016-02-21T16:23:45.420
+ 2023-03-27T13:28:35.381Z
+ 0
+ false
+ 0
+ false
+ 1
+
+
+ This is the description
+
+
+
+
+ animal
+ bird
+
+
+
+
+ sample title
+
+
+ 2016-02-21T16:23:45.420
+ 53,13.1635N
+ 8,14.3797E
+ 2.2.0.0
+
+
+ 400
+
+
+ 420
+ 420
+ 420
+
+
+
+
+ 3000
+ pixel
+ 4512
+
+
+
+
+
+
+
+
+ 0.050000
+ normalized
+ 0.033245
+ 0.295179
+ 0.278880
+
+
+ Eye Left
+ Face
+
+
+
+
+
+
+ 0.050000
+ normalized
+ 0.033245
+ 0.814990
+ 0.472579
+
+
+ Eye Right
+ Face
+
+
+
+
+
+
+ 0.050000
+ normalized
+ 0.033245
+ 0.501552
+ 0.905484
+
+
+ Nothing
+ Face
+
+
+
+
+
+
+ 1
+ 2016-02-21T16:23:45.420
+ Green
+ 2023-03-27T13:28:35.381Z
+ 2016-02-21T16:23:45.420
+ -1
+ 26D2F494-1226-403D-BAB5-F41D05F2AB35
+ xmp.iid:3740FF85-236E-4C0D-8131-39CCEADD1BA1
+ 26D2F494-1226-403D-BAB5-F41D05F2AB35
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_48_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_48_formatted_compact.xmp
new file mode 100644
index 0000000..b385a14
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_48_formatted_compact.xmp
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+ Eye Left
+ Eye Right
+ Nothing
+
+
+
+
+ This is the description
+
+
+
+
+ animal
+ bird
+
+
+
+
+ sample title
+
+
+
+
+ 400
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_49.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_49.xmp
new file mode 100644
index 0000000..3409e90
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_49.xmp
@@ -0,0 +1,18 @@
+0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_49_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_49_formatted_canonical.xmp
new file mode 100644
index 0000000..63bda32
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_49_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_49_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_49_formatted_compact.xmp
new file mode 100644
index 0000000..7fec24e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_49_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_4_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_4_formatted_canonical.xmp
new file mode 100644
index 0000000..ab761ba
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_4_formatted_canonical.xmp
@@ -0,0 +1,290 @@
+
+
+
+
+ 67/100
+ 1.1.5
+ 0/1
+ 0
+ True
+ EF-S18-55mm f/3.5-5.6 IS STM
+ 4146
+ 18/1 55/1 0/0 0/0
+ 0000070a01
+ 322034001175
+ True
+ 1
+ -9
+ -10
+ -10
+ Adobe Standard
+ BA45C872F6A5D11497D00CBA08D5783F
+ 0
+ 25
+ 50
+ 50
+ -18
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.95
+ 22
+ 50
+ 3847824574
+ 45
+ +50
+ -25
+ False
+ True
+ -94
+ 0
+ +2
+ -10
+ 0
+ -15
+ 0
+ -18
+ -9
+ 0
+ 0
+ LensDefaults
+
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 12.1
+
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+
+ 0
+ -70
+ +17
+ 0
+ -39
+ 0
+ -23
+ -21
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ IMG_9499.CR2
+ +46
+ +5
+ -35
+ -51
+ -19
+ -17
+ 0
+ +16
+ 0
+ +5
+ -15
+ 0
+ +55
+ 25
+ 0
+ +1.0
+ 35
+ 0
+ 34
+ 0
+ 52
+ 20
+ 4659
+ 0
+ +14
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 0
+ 59, 62
+ 177, 163
+ 255, 211
+
+
+
+
+ 0, 0
+ 39, 13
+ 87, 65
+ 123, 124
+ 183, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 14
+ 83, 59
+ 123, 126
+ 172, 185
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 12
+ 84, 58
+ 123, 125
+ 186, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 12.1
+ -4
+ 0
+ Custom
+ -99
+ image/jpeg
+ EF-S18-55mm f/3.5-5.6 IS STM
+ 2019-12-23T01:28:28.23
+ 2019-12-23T01:28:28.23
+ Adobe Photoshop Lightroom Classic 9.1 (Macintosh)
+ 2019-12-23T13:28:07-05:00
+ 2019-12-23T13:28:07-05:00
+ 1
+
+
+ D749B1BD13ECB96B04E98B6E2C04E2ED
+ D749B1BD13ECB96B04E98B6E2C04E2ED
+
+
+ xmp.did:61d558ea-ab7f-469a-8e30-064f02333d6c
+
+
+
+
+ derived
+ converted from image/x-canon-cr2 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:61d558ea-ab7f-469a-8e30-064f02333d6c
+ Adobe Photoshop Lightroom Classic 9.1 (Macintosh)
+ 2019-12-23T13:28:07-05:00
+
+
+
+
+ xmp.iid:61d558ea-ab7f-469a-8e30-064f02333d6c
+ D749B1BD13ECB96B04E98B6E2C04E2ED
+ IMG_9499.CR2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_4_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_4_formatted_compact.xmp
new file mode 100644
index 0000000..dd6ce34
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_4_formatted_compact.xmp
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+ Profiles
+
+
+
+
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 59, 62
+ 177, 163
+ 255, 211
+
+
+
+
+ 0, 0
+ 39, 13
+ 87, 65
+ 123, 124
+ 183, 199
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 14
+ 83, 59
+ 123, 126
+ 172, 185
+ 255, 255
+
+
+
+
+ 0, 0
+ 40, 12
+ 84, 58
+ 123, 125
+ 186, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_5.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_5.xmp
new file mode 100644
index 0000000..b426a91
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_5.xmp
@@ -0,0 +1,353 @@
+
+
+
+
+
+ True
+ XF90mmF2 R LM WR
+ 9000/100 9000/100 200/100 200/100
+ 65B00914
+ 7AA03899
+ True
+
+
+
+ image/jpeg
+
+
+ london
+
+
+
+
+
+ XF90mmF2 R LM WR
+
+
+
+ 2019-01-21T20:13:58
+
+
+
+ 2019-01-21T20:13:58
+ Adobe Photoshop Lightroom Classic 8.1 (Macintosh)
+ 2019-01-22T20:56:19Z
+ 2019-01-22T20:56:19Z
+ 5
+
+
+
+
+ 369B2D87DBEB889F004D361C07FB2970
+ 369B2D87DBEB889F004D361C07FB2970
+
+ xmp.did:1d14d048-4182-40ba-b961-63f22114306e
+
+
+
+ derived
+ converted from image/x-fuji-raw to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:1d14d048-4182-40ba-b961-63f22114306e
+ Adobe Photoshop Lightroom Classic 8.1 (Macintosh)
+ 2019-01-22T20:56:19Z
+
+
+
+ xmp.iid:1d14d048-4182-40ba-b961-63f22114306e
+ 369B2D87DBEB889F004D361C07FB2970
+
+
+
+ True
+ 1
+ -6
+ 0
+ 0
+ Adobe Standard
+ 272224000D5765B749BB38DB525CB88C
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+ 0.503248
+ +50
+ true
+ 0.491508
+ 1.000000
+ +50
+ 0.594848
+ 0
+ 0.328390
+ +2
+ Mask/CircularGradient
+
+
+
+
+ 0.500000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0
+ +2
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.299758
+ 0.000000
+ 0.000000
+ 340.781006
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 0.757068
+ 0
+ 0.210608
+ 0.815437
+ 0.246744
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ 0
+ 0
+ True
+ True
+ -51
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Vivid
+
+ Adobe Standard
+ +10
+ False
+ 2FE663AB0D3CE5DA7B9F657BBCD66DFE
+ 11.0
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 224, 232
+ 240, 246
+ 255, 255
+
+
+ 11.1
+
+ false
+ false
+ false
+ EA1DE074F188405965EF399C72C221D9
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 8
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ DSCF2429.RAF
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ -15
+ 0
+ 0
+ +20
+ 25
+ 0
+ +1.0
+ 44
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5490
+ +17
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 8
+ 45, 47
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.1
+ 0
+ 0
+ Custom
+ +45
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_50.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_50.xmp
new file mode 100644
index 0000000..bc19b36
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_50.xmp
@@ -0,0 +1,4 @@
+
+0
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_50_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_50_formatted_canonical.xmp
new file mode 100644
index 0000000..63bda32
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_50_formatted_canonical.xmp
@@ -0,0 +1,10 @@
+
+
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_50_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_50_formatted_compact.xmp
new file mode 100644
index 0000000..7fec24e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_50_formatted_compact.xmp
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_51.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_51.xmp
new file mode 100644
index 0000000..bd656cf
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_51.xmp
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SippakorN
+
+
+
+
+ @Sippakorn1955
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_51_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_51_formatted_canonical.xmp
new file mode 100644
index 0000000..c5ab663
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_51_formatted_canonical.xmp
@@ -0,0 +1,233 @@
+
+
+
+
+ 2.0
+ Mac OS
+ 1664194672787493
+ 2.10.30
+ 161/100
+ 1.4.0
+ 0/1
+ 0
+ EF70-300mm f/4-5.6L IS USM
+ 489
+ 70/1 300/1 0/0 0/0
+ 7820004130
+ 058021001133
+ True
+ 0
+ DB958B423B648352F5500DCF12E528B1
+ 5D637BD0F4976718D5C101714DE7835E
+ -27
+ 0
+ 0
+ Adobe Standard
+ 38E531B7F49849F74EF6FA15106BD31D
+ -15
+ 25
+ 23
+ 23
+ +56
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +11
+ +0.20
+ 0
+ 0
+ 0
+ False
+ True
+ -33
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+ 1.000000
+ © 2018 Adobe Systems, Inc.
+ lang="x-default" Profiles
+ Adobe Color
+
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+ 0, 0, 22, 16, 40, 35, 127, 127, 224, 230, 240, 246, 255, 255
+ 0, 0, 255, 255
+ 0, 0, 255, 255
+ 0, 0, 255, 255
+ 12.2
+
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 21
+ 20
+ 20
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ 353A9054.CR3
+ 0
+ 0
+ -1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +42
+ 25
+ 0
+ +1.0
+ 40
+ 0
+ 0
+ 0
+ 0
+ 0
+ 4850
+ -9
+ -7
+ 0, 0, 32, 22, 64, 56, 128, 128, 192, 196, 255, 255
+ 0, 0, 255, 255
+ 0, 0, 255, 255
+ Medium Contrast
+ Linear
+ 0, 0, 255, 255
+ 0, 0, 255, 255
+ 0, 0, 255, 255
+ 0, 0, 255, 255
+ 0, 0, 255, 255
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 12.2
+ +14
+ 0
+ Custom
+ +36
+
+
+ SippakorN
+
+
+ image/jpeg
+
+
+ @Sippakorn1955
+
+
+ EF70-300mm f/4-5.6L IS USM
+ 2020-03-11T17:37:33.36+08:00
+ 1
+ 2020-03-11T17:37:33.36+08:00
+ GIMP 2.10
+ 2020-03-11T20:10:12+07:00
+ 2020-03-11T20:10:12+07:00
+ 2
+
+
+ D91E7F7E064AEF7A630A1DCAC6BF38D0
+ D91E7F7E064AEF7A630A1DCAC6BF38D0
+
+
+ xmp.did:69493116-46dc-402d-931b-1c9bf37ddaad
+
+
+
+
+ derived
+ converted from image/x-canon-cr3 to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:69493116-46dc-402d-931b-1c9bf37ddaad
+ Adobe Photoshop Camera Raw 12.2 (Macintosh)
+ 2020-03-11T20:10:12+07:00
+
+
+
+
+ saved
+ /
+ xmp.iid:9d7b157e-f1a6-4fbe-8b35-d7cf3cf75dd3
+ Gimp 2.10 (Mac OS)
+ 2022-09-26T14:17:52+02:00
+
+
+
+
+ xmp.iid:b6251a9e-0b38-4a48-b01e-cf5a7d9a4bb9
+ D91E7F7E064AEF7A630A1DCAC6BF38D0
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_51_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_51_formatted_compact.xmp
new file mode 100644
index 0000000..9e5e197
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_51_formatted_compact.xmp
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+
+
+
+
+
+ SippakorN
+
+
+
+
+ @Sippakorn1955
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_52.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_52.xmp
new file mode 100644
index 0000000..4519be8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_52.xmp
@@ -0,0 +1,250 @@
+
+
+
+
+
+ dxfoto|published
+
+
+ Firmware Version 1.1.2
+ 55/1 250/1 0/0 0/0
+ Canon EF-S 55-250mm f/4-5.6 IS STM
+ 4148
+ 0/1
+ 0
+ 1
+ 2670
+ 208885/36926
+
+
+ 250
+
+
+ 55,54.679N
+ 36,57.8089E
+ 2
+ 1
+ 2.2.0.0
+ 368640/65536
+ 0100
+ 47
+ 3648000/599
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+ 1/400
+ E
+ 250/1
+ 0
+ 565248/65536
+ 47
+ N
+ 0
+ 1
+ 71/10
+ 5
+
+ False
+ False
+ 0
+ 2
+ False
+
+ 5472000/899
+ 0/1
+ 4000
+ 0230
+ 47
+ 103025021031
+ 250
+ 250
+ EF-S55-250mm f/4-5.6 IS STM
+ 0000800dcf
+ 2
+
+
+ 55/1
+ 250/1
+ 0/1
+ 0/1
+
+
+ 2019-07-15T20:09:19.47
+ 0
+ 2019-07-14T09:21:11.47
+ darktable 2.6.2
+ 1
+
+
+
+ 000000400000003f0000003f
+ sharpen
+ 1
+ 1
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ ffffffff
+ flip
+ 1
+ 2
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ gz09eJxjYIAAM6vnNnqyn22E9n235b6aa3cy6rVdRaK9/Y970fYf95bbMzA0QPEoGEqADYnNhCELiVMAudcSGA==
+ basecurve
+ 1
+ 5
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 00000000e040f23c4ee4053efce6413f80c75b3f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+ clipping
+ 1
+ 5
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 00000000501a1c3d0c5cb53d9a16443feef9503f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+ clipping
+ 1
+ 5
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ d0ccccbdd0cccc3dd0cc4cbd
+ colisa
+ 1
+ 1
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 01000000cdcccc3ecdcc8c3f0000803ecdcc4c3e
+ bilat
+ 1
+ 3
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 000000400000803f0000003f
+ sharpen
+ 1
+ 1
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 01000000cdcccc3ecdcc8c3f9a99993ecdcc4c3e
+ bilat
+ 1
+ 3
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 0000000000000000000048420000c842d9899d3dc332d53e8bac483f
+ levels
+ 1
+ 2
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 0000000000000000000000000100000000000000
+ demosaic
+ 1
+ 3
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+ 32000000
+ cacorrect
+ 1
+ 1
+ 8
+ 0
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+
+
+
+
+ 12
+ 0
+ 2
+ 1
+ 300
+ 2
+ Canon
+ 300
+ Canon EOS 70D
+
+
+ dxfoto
+ published
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ honeybee perching on yellow flower
+
+
+
+
+ dxfoto.ru
+
+
+ 2019-07-14T09:21:11.47
+ _MG_9604.CR2
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_52_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_52_formatted_canonical.xmp
new file mode 100644
index 0000000..f655382
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_52_formatted_canonical.xmp
@@ -0,0 +1,278 @@
+
+
+
+
+ Firmware Version 1.1.2
+ 0/1
+ Canon EF-S 55-250mm f/4-5.6 IS STM
+ 4148
+ 55/1 250/1 0/0 0/0
+ 1
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ sharpen
+ 000000400000003f0000003f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+
+ 0
+ flip
+ ffffffff
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ basecurve
+ gz09eJxjYIAAM6vnNnqyn22E9n235b6aa3cy6rVdRaK9/Y970fYf95bbMzA0QPEoGEqADYnNhCELiVMAudcSGA==
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ clipping
+ 00000000e040f23c4ee4053efce6413f80c75b3f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+
+ 0
+ clipping
+ 00000000501a1c3d0c5cb53d9a16443feef9503f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ colisa
+ d0ccccbdd0cccc3dd0cc4cbd
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f0000803ecdcc4c3e
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ sharpen
+ 000000400000803f0000003f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f9a99993ecdcc4c3e
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+
+ 0
+ levels
+ 0000000000000000000048420000c842d9899d3dc332d53e8bac483f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+
+ 0
+ demosaic
+ 0000000000000000000000000100000000000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+
+ 0
+ cacorrect
+ 32000000
+
+
+
+
+ 12
+ 0
+ 2
+
+
+ dxfoto.ru
+
+
+
+
+ honeybee perching on yellow flower
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+ 368640/65536
+ 1
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+ 0
+ 0230
+ 0/1
+ 1
+ 1
+ 1/400
+ 71/10
+
+
+ False
+ False
+ 2
+ False
+ 0
+
+
+ 0100
+ 250/1
+ 2
+ 5472000/899
+ 3648000/599
+ 55,54.679N
+ N
+ 36,57.8089E
+ E
+ 2.2.0.0
+
+
+ 250
+
+
+ 208885/36926
+ 5
+ 4000
+ 2670
+ 0
+ 565248/65536
+ 47
+ 47
+ 47
+ 0
+ 103025021031
+ EF-S55-250mm f/4-5.6 IS STM
+ 0000800dcf
+
+
+ 55/1
+ 250/1
+ 0/1
+ 0/1
+
+
+ 250
+ 250
+ 2
+
+
+ dxfoto|published
+
+
+ 2019-07-14T09:21:11.47
+ Canon
+ Canon EOS 70D
+ 1
+ 2
+ 300
+ 300
+ 2019-07-14T09:21:11.47
+ darktable 2.6.2
+ 2019-07-15T20:09:19.47
+ 0
+ _MG_9604.CR2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_52_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_52_formatted_compact.xmp
new file mode 100644
index 0000000..1b44f14
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_52_formatted_compact.xmp
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dxfoto.ru
+
+
+
+
+ honeybee perching on yellow flower
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+
+
+ 1
+ 2
+ 3
+ 0
+
+
+
+
+
+ 250
+
+
+
+
+ 55/1
+ 250/1
+ 0/1
+ 0/1
+
+
+
+
+ dxfoto|published
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_53.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_53.xmp
new file mode 100644
index 0000000..5e41bc4
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_53.xmp
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dxfoto.ru
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+
+
+ dxfoto|published
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_53_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_53_formatted_canonical.xmp
new file mode 100644
index 0000000..4db0c34
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_53_formatted_canonical.xmp
@@ -0,0 +1,206 @@
+
+
+
+
+ 2.0
+ Mac OS
+ 1683556949179434
+ 2.10.30
+ 1
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+ 0
+ sharpen
+ 000000400000003f0000003f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+ 0
+ flip
+ ffffffff
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+ 0
+ basecurve
+ gz09eJxjYIAAM6vnNnqyn22E9n235b6aa3cy6rVdRaK9/Y970fYf95bbMzA0QPEoGEqADYnNhCELiVMAudcSGA==
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+ 0
+ clipping
+ 00000000e040f23c4ee4053efce6413f80c75b3f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 5
+ 0
+ clipping
+ 00000000501a1c3d0c5cb53d9a16443feef9503f0000000000000000cdcc4c3ecdcc4c3ecdcc4c3fcdcc4c3ecdcc4c3fcdcc4c3fcdcc4c3ecdcc4c3f000000000000000000000000010000000000000001000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+ 0
+ colisa
+ d0ccccbdd0cccc3dd0cc4cbd
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f0000803ecdcc4c3e
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+ 0
+ sharpen
+ 000000400000803f0000003f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+ 0
+ bilat
+ 01000000cdcccc3ecdcc8c3f9a99993ecdcc4c3e
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 2
+ 0
+ levels
+ 0000000000000000000048420000c842d9899d3dc332d53e8bac483f
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 3
+ 0
+ demosaic
+ 0000000000000000000000000100000000000000
+
+
+
+
+ gz11eJxjYGBgkGAAgRNODGiAEV0AJ2iwh+CRxQcA5qIZBA==
+ 8
+ 1
+ 1
+ 0
+ cacorrect
+ 32000000
+
+
+
+
+ 12
+ 0
+ 2
+ image/png
+
+
+ dxfoto.ru
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+ 55,54.678955N
+ 36,57.808914E
+ 2.2.0.0
+
+
+ dxfoto|published
+
+
+ 1
+ GIMP 2.10
+ 0
+ _MG_9604.CR2
+ gimp:docid:gimp:dab56eec-4e79-4d04-8b3c-3d000bcabc0a
+
+
+
+
+ saved
+ /
+ xmp.iid:4ddb99b5-d320-4bd0-918a-8f68cea699a6
+ Gimp 2.10 (Mac OS)
+ 2023-05-08T16:42:29+02:00
+
+
+
+
+ xmp.iid:f1488bc5-a1a0-486a-9dc2-4cb2621a1acd
+ xmp.did:cb97ab9c-80d0-4ee0-aa3f-96f0fdafba3e
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_53_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_53_formatted_compact.xmp
new file mode 100644
index 0000000..9ce5050
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_53_formatted_compact.xmp
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dxfoto.ru
+
+
+
+
+ CC-BY 3.0
+
+
+
+
+ dxfoto
+ published
+
+
+
+
+ dxfoto|published
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_54.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_54.xmp
new file mode 100644
index 0000000..851a839
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_54.xmp
@@ -0,0 +1,88 @@
+
+
+
+
+ 2018-10-20T15:19:58.620
+ 620
+ 620
+ 620
+
+
+ 125
+
+
+ 2018-10-20T15:19:58.620
+ 2018-10-20T15:19:58.620
+ 5
+ 2023-03-24T08:34:52.699Z
+ 2018-10-20T15:19:58.620
+ 2018-10-20T15:19:58.620
+
+ false
+ false
+ 1
+ 2023-03-24T08:34:52.699Z
+ 3FA43AE2-4074-445A-99A3-6331901004DA
+ 3FA43AE2-4074-445A-99A3-6331901004DA
+ xmp.iid:5E9C720A-E628-4EA0-94AB-ACF8D1722FE9
+
+
+ orange fox walking on street
+
+
+
+
+ "fuchs"
+ <HALLO>
+ fox
+ fuchs
+ fuchs = "süß"
+ süßer fuchs
+ was solls
+
+
+
+
+ 2014
+
+
+
+
+ 4390
+ 2927
+ pixel
+
+
+
+
+ Face
+ Swiper
+ mylio_face
+
+ 0.404336
+ 0.422313
+ 0.124503
+ 0.240097
+ normalized
+
+
+
+
+
+
+
+ Swiper
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_54_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_54_formatted_canonical.xmp
new file mode 100644
index 0000000..c91a9c0
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_54_formatted_canonical.xmp
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+ Swiper
+
+
+ 2018-10-20T15:19:58.620
+
+ 2018-10-20T15:19:58.620
+ 2023-03-24T08:34:52.699Z
+ false
+ false
+ 1
+
+
+ orange fox walking on street
+
+
+
+
+ 2014
+
+
+
+
+ "fuchs"
+ <HALLO>
+ fox
+ fuchs
+ fuchs = "süß"
+ süßer fuchs
+ was solls
+
+
+ 2018-10-20T15:19:58.620
+
+
+ 125
+
+
+ 620
+ 620
+ 620
+
+
+
+
+ 2927
+ pixel
+ 4390
+
+
+
+
+
+
+
+
+ 0.240097
+ normalized
+ 0.124503
+ 0.404336
+ 0.422313
+
+
+ mylio_face
+ Swiper
+ Face
+
+
+
+
+
+
+ 2018-10-20T15:19:58.620
+ 2023-03-24T08:34:52.699Z
+ 2018-10-20T15:19:58.620
+ 5
+ 3FA43AE2-4074-445A-99A3-6331901004DA
+ xmp.iid:5E9C720A-E628-4EA0-94AB-ACF8D1722FE9
+ 3FA43AE2-4074-445A-99A3-6331901004DA
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_54_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_54_formatted_compact.xmp
new file mode 100644
index 0000000..264079b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_54_formatted_compact.xmp
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+ Swiper
+
+
+
+
+ orange fox walking on street
+
+
+
+
+ 2014
+
+
+
+
+ "fuchs"
+ <HALLO>
+ fox
+ fuchs
+ fuchs = "süß"
+ süßer fuchs
+ was solls
+
+
+
+
+ 125
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_55.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_55.xmp
new file mode 100644
index 0000000..433b543
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_55.xmp
@@ -0,0 +1,11 @@
+
+
+
+
+ 6
+ 4
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_55_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_55_formatted_canonical.xmp
new file mode 100644
index 0000000..affaf98
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_55_formatted_canonical.xmp
@@ -0,0 +1,12 @@
+
+
+
+
+ 6
+ 4
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_55_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_55_formatted_compact.xmp
new file mode 100644
index 0000000..3d749b5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_55_formatted_compact.xmp
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_56.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_56.xmp
new file mode 100644
index 0000000..4b40f3a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_56.xmp
@@ -0,0 +1 @@
+ test
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_56_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_56_formatted_canonical.xmp
new file mode 100644
index 0000000..3e5815a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_56_formatted_canonical.xmp
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ test
+
+
+ 2023-07-03T10:24:16+02:00
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_56_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_56_formatted_compact.xmp
new file mode 100644
index 0000000..62cb6e3
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_56_formatted_compact.xmp
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ test
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_57.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_57.xmp
new file mode 100644
index 0000000..e356f61
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_57.xmp
@@ -0,0 +1,110 @@
+
+
+
+
+ 2023-07-03T10:24:15.000
+ Picasa
+ 2023-07-03T10:24:15.000
+ 2023-07-03T08:38:49.983Z
+
+ 0
+ 2023-07-03T10:24:15.000
+ 2023-07-03T10:24:15.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:49.983Z
+
+ 0.200000
+ true
+ 0.062500
+ 0.925781
+ 0.078125
+ 0.824219
+ 0.062500
+ 0.886719
+ 78C153E3-9822-4D77-AF78-8F298A87C094
+ 78C153E3-9822-4D77-AF78-8F298A87C094
+ xmp.iid:AF118F18-77FE-4563-81AB-8B8B4D00529F
+ 2023-07-03T10:24:15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_57_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_57_formatted_canonical.xmp
new file mode 100644
index 0000000..cbbad91
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_57_formatted_canonical.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+ 2023-07-03T10:24:15.000
+
+ 2023-07-03T10:24:15.000
+ 2023-07-03T08:38:49.983Z
+ false
+ false
+ 0.824219
+ 0.078125
+ true
+ 0.886719
+ 0.062500
+ 0.925781
+ 0.062500
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 0
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2023-07-03T10:24:15
+ 2023-07-03T10:24:15.000
+ Picasa
+
+ 2023-07-03T08:38:49.983Z
+ 2023-07-03T10:24:15.000
+ 0
+ 78C153E3-9822-4D77-AF78-8F298A87C094
+ xmp.iid:AF118F18-77FE-4563-81AB-8B8B4D00529F
+ 78C153E3-9822-4D77-AF78-8F298A87C094
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_57_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_57_formatted_compact.xmp
new file mode 100644
index 0000000..9d9c4af
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_57_formatted_compact.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_58.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_58.xmp
new file mode 100644
index 0000000..ec31de3
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_58.xmp
@@ -0,0 +1,110 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.014Z
+
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.014Z
+
+ 0.200000
+ true
+ 0.050781
+ 0.921875
+ 0.093750
+ 0.917969
+ 0.058594
+ 0.910156
+ CE4B64B1-AAFF-4795-AA61-9475FF7EBB36
+ CE4B64B1-AAFF-4795-AA61-9475FF7EBB36
+ xmp.iid:0FD5160D-4896-48B8-95F8-B72B27CD8230
+ 2023-07-03T10:24:16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_58_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_58_formatted_canonical.xmp
new file mode 100644
index 0000000..d00b07c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_58_formatted_canonical.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.014Z
+ false
+ false
+ 0.917969
+ 0.093750
+ true
+ 0.910156
+ 0.058594
+ 0.921875
+ 0.050781
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 0
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.014Z
+ 2023-07-03T10:24:16.000
+ 0
+ CE4B64B1-AAFF-4795-AA61-9475FF7EBB36
+ xmp.iid:0FD5160D-4896-48B8-95F8-B72B27CD8230
+ CE4B64B1-AAFF-4795-AA61-9475FF7EBB36
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_58_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_58_formatted_compact.xmp
new file mode 100644
index 0000000..9aeac63
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_58_formatted_compact.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_59.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_59.xmp
new file mode 100644
index 0000000..c9f8ec8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_59.xmp
@@ -0,0 +1,110 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+ Picasa
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.046Z
+ 0
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T10:24:17.000
+ false
+ true
+ 1
+ 2023-07-03T08:38:50.046Z
+
+ 0.200000
+ true
+ 0.062500
+ 0.949219
+ 0.050781
+ 0.933594
+ 0.062500
+ 0.910156
+ 3429EBFC-4E41-4534-96C7-D47BF942AC6E
+ 3429EBFC-4E41-4534-96C7-D47BF942AC6E
+ xmp.iid:D3C2DC75-C941-4497-B944-BB2424F6FD98
+ 2023-07-03T10:24:17
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_59_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_59_formatted_canonical.xmp
new file mode 100644
index 0000000..f08bbc2
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_59_formatted_canonical.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.046Z
+ false
+ true
+ 0.933594
+ 0.050781
+ true
+ 0.910156
+ 0.062500
+ 0.949219
+ 0.062500
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 0
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2023-07-03T10:24:17
+ 2023-07-03T10:24:17.000
+ Picasa
+
+ 2023-07-03T08:38:50.046Z
+ 2023-07-03T10:24:17.000
+ 0
+ 3429EBFC-4E41-4534-96C7-D47BF942AC6E
+ xmp.iid:D3C2DC75-C941-4497-B944-BB2424F6FD98
+ 3429EBFC-4E41-4534-96C7-D47BF942AC6E
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_59_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_59_formatted_compact.xmp
new file mode 100644
index 0000000..cbf517a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_59_formatted_compact.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_5_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_5_formatted_canonical.xmp
new file mode 100644
index 0000000..2e01fc7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_5_formatted_canonical.xmp
@@ -0,0 +1,326 @@
+
+
+
+
+ True
+ XF90mmF2 R LM WR
+ 9000/100 9000/100 200/100 200/100
+ 65B00914
+ 7AA03899
+ True
+ True
+ 1
+ -6
+ 0
+ 0
+ Adobe Standard
+ 272224000D5765B749BB38DB525CB88C
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+ 0.503248
+ +50
+ true
+ 0.491508
+ 1.000000
+ +50
+ 0.594848
+ 0
+ 0.328390
+ +2
+ Mask/CircularGradient
+
+
+
+
+
+
+ 0.500000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0
+ +2
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.299758
+ 0.000000
+ 0.000000
+ 340.781006
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ 0
+ 25
+ 50
+ 50
+ 0
+ False
+ 0
+ 0.757068
+ 0
+ 0.210608
+ 0.815437
+ 0.246744
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ 0.00
+ 0
+ 0
+ 0
+ True
+ True
+ -51
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Vivid
+
+
+ Adobe Standard
+ +10
+ False
+ 2FE663AB0D3CE5DA7B9F657BBCD66DFE
+ 11.0
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 224, 232
+ 240, 246
+ 255, 255
+
+
+ 11.1
+
+
+ false
+ false
+ false
+ EA1DE074F188405965EF399C72C221D9
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 50
+ 8
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ DSCF2429.RAF
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ -15
+ 0
+ 0
+ +20
+ 25
+ 0
+ +1.0
+ 44
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5490
+ +17
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 0, 8
+ 45, 47
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.1
+ 0
+ 0
+ Custom
+ +45
+ image/jpeg
+
+
+ london
+
+
+ XF90mmF2 R LM WR
+ 2019-01-21T20:13:58
+ 2019-01-21T20:13:58
+ Adobe Photoshop Lightroom Classic 8.1 (Macintosh)
+ 2019-01-22T20:56:19Z
+ 2019-01-22T20:56:19Z
+ 5
+
+
+ 369B2D87DBEB889F004D361C07FB2970
+ 369B2D87DBEB889F004D361C07FB2970
+
+
+ xmp.did:1d14d048-4182-40ba-b961-63f22114306e
+
+
+
+
+ derived
+ converted from image/x-fuji-raw to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:1d14d048-4182-40ba-b961-63f22114306e
+ Adobe Photoshop Lightroom Classic 8.1 (Macintosh)
+ 2019-01-22T20:56:19Z
+
+
+
+
+ xmp.iid:1d14d048-4182-40ba-b961-63f22114306e
+ 369B2D87DBEB889F004D361C07FB2970
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_5_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_5_formatted_compact.xmp
new file mode 100644
index 0000000..12d4600
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_5_formatted_compact.xmp
@@ -0,0 +1,311 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Profiles
+
+
+
+
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 224, 232
+ 240, 246
+ 255, 255
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 8
+ 45, 47
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ london
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_6.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_6.xmp
new file mode 100644
index 0000000..756e515
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_6.xmp
@@ -0,0 +1,271 @@
+
+
+
+
+
+ True
+ True
+ 28-100mm F1.8-4.9
+ 34683/32768 -219328315/1073741824 52441832/1073741824 -7678158/1073741824
+ 1040/100 3710/100 18/10 49/10
+
+
+
+ image/jpeg
+
+
+
+ 28-100mm F1.8-4.9
+
+
+
+ 2019-02-25T06:14:16
+
+
+
+ 2019-02-25T06:14:16
+ Adobe Photoshop Lightroom Classic 8.3.1 (Windows)
+ 2019-08-04T21:22:41+02:00
+ 2019-08-04T21:22:41+02:00
+ 0
+
+
+
+
+ adobe:docid:photoshop:402c59b6-9b47-284c-bf69-c29ff9849814
+ xmp.iid:5ed51343-dc41-7a4d-bbc9-d779a5a42cf5
+ F44F7718D2EE77F231D30D6F8BD43643
+
+ xmp.did:ce2000f3-5b24-1346-bf4e-cd945cb900a8
+
+
+
+ derived
+ converted from image/x-sony-arw to image/tiff
+
+
+ saved
+ /
+ xmp.iid:1b7c7f7f-f2d3-3644-beb2-46bf86b3b9f7
+ Adobe Photoshop Camera Raw 11.0 (Windows)
+ 2019-03-14T22:13:19+01:00
+
+
+ saved
+ /
+ xmp.iid:5ed51343-dc41-7a4d-bbc9-d779a5a42cf5
+ Adobe Photoshop CC 2019 (Windows)
+ 2019-03-14T22:29:25+01:00
+
+
+ derived
+ converted from image/tiff to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:ce2000f3-5b24-1346-bf4e-cd945cb900a8
+ Adobe Photoshop Lightroom Classic 8.3.1 (Windows)
+ 2019-08-04T21:22:41+02:00
+
+
+
+ xmp.iid:ce2000f3-5b24-1346-bf4e-cd945cb900a8
+ F44F7718D2EE77F231D30D6F8BD43643
+
+
+
+ True
+ 0
+ 0
+ 0
+ 0
+ Embedded
+ 54650A341B5B5CCAE8442D0B43A92BCE
+ 0
+ 0
+ 0
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.40
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.3
+ 0
+ 0
+ As Shot
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_60.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_60.xmp
new file mode 100644
index 0000000..7d94778
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_60.xmp
@@ -0,0 +1,110 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.077Z
+ 0
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ true
+ 1
+ 2023-07-03T08:38:50.077Z
+
+ 0.200000
+ true
+ 0.054688
+ 0.992188
+ 0.046875
+ 0.992188
+ 0.054688
+ 0.992188
+ AED373B5-286A-4A0C-A374-0BA30B853904
+ AED373B5-286A-4A0C-A374-0BA30B853904
+ xmp.iid:A755C97A-A0D1-4BFA-B5EC-67952B46BA77
+ 2023-07-03T10:24:16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_60_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_60_formatted_canonical.xmp
new file mode 100644
index 0000000..6a67cb9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_60_formatted_canonical.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.077Z
+ false
+ true
+ 0.992188
+ 0.046875
+ true
+ 0.992188
+ 0.054688
+ 0.992188
+ 0.054688
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 0
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.077Z
+ 2023-07-03T10:24:16.000
+ 0
+ AED373B5-286A-4A0C-A374-0BA30B853904
+ xmp.iid:A755C97A-A0D1-4BFA-B5EC-67952B46BA77
+ AED373B5-286A-4A0C-A374-0BA30B853904
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_60_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_60_formatted_compact.xmp
new file mode 100644
index 0000000..e7a6987
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_60_formatted_compact.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_61.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_61.xmp
new file mode 100644
index 0000000..a003a00
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_61.xmp
@@ -0,0 +1,110 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+ Picasa
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.124Z
+ 0
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T10:24:17.000
+ false
+ true
+ 1
+ 2023-07-03T08:38:50.124Z
+
+ 0.200000
+ true
+ 0.039062
+ 0.523438
+ 0.027344
+ 0.359375
+ 0.039062
+ 0.546875
+ 4107C8E7-F5B1-4E2C-98BB-E72BC5DDAEA1
+ 4107C8E7-F5B1-4E2C-98BB-E72BC5DDAEA1
+ xmp.iid:20D9E086-AB19-4FE2-96AC-65B13E0A5F96
+ 2023-07-03T10:24:17
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_61_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_61_formatted_canonical.xmp
new file mode 100644
index 0000000..238a71b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_61_formatted_canonical.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.124Z
+ false
+ true
+ 0.359375
+ 0.027344
+ true
+ 0.546875
+ 0.039062
+ 0.523438
+ 0.039062
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 0
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2023-07-03T10:24:17
+ 2023-07-03T10:24:17.000
+ Picasa
+
+ 2023-07-03T08:38:50.124Z
+ 2023-07-03T10:24:17.000
+ 0
+ 4107C8E7-F5B1-4E2C-98BB-E72BC5DDAEA1
+ xmp.iid:20D9E086-AB19-4FE2-96AC-65B13E0A5F96
+ 4107C8E7-F5B1-4E2C-98BB-E72BC5DDAEA1
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_61_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_61_formatted_compact.xmp
new file mode 100644
index 0000000..d802ce6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_61_formatted_compact.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_62.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_62.xmp
new file mode 100644
index 0000000..c673b73
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_62.xmp
@@ -0,0 +1,110 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.171Z
+
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.171Z
+
+ 0.200000
+ true
+ 0.042969
+ 0.937500
+ 0.035156
+ 0.929688
+ 0.042969
+ 0.933594
+ 21162A17-7409-4F90-A0C3-482A7E5E15CF
+ 21162A17-7409-4F90-A0C3-482A7E5E15CF
+ xmp.iid:0789E160-3560-456F-9553-DEB1BA758BB1
+ 2023-07-03T10:24:16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_62_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_62_formatted_canonical.xmp
new file mode 100644
index 0000000..71dc00b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_62_formatted_canonical.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.171Z
+ false
+ false
+ 0.929688
+ 0.035156
+ true
+ 0.933594
+ 0.042969
+ 0.937500
+ 0.042969
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 0
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.171Z
+ 2023-07-03T10:24:16.000
+ 0
+ 21162A17-7409-4F90-A0C3-482A7E5E15CF
+ xmp.iid:0789E160-3560-456F-9553-DEB1BA758BB1
+ 21162A17-7409-4F90-A0C3-482A7E5E15CF
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_62_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_62_formatted_compact.xmp
new file mode 100644
index 0000000..c7b868e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_62_formatted_compact.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_63.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_63.xmp
new file mode 100644
index 0000000..367827f
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_63.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+ Picasa
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.234Z
+ 3
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T10:24:17.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.234Z
+
+ 0.200000
+ true
+ 0.035156
+ 0.957031
+ 0.035156
+ 0.937500
+ 0.035156
+ 0.949219
+ E2727A18-83AD-4FF2-88AD-A0F8928A92FF
+ E2727A18-83AD-4FF2-88AD-A0F8928A92FF
+ xmp.iid:CE151D84-1C9F-4732-8995-0DB221EB2D40
+ 2023-07-03T10:24:17
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_63_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_63_formatted_canonical.xmp
new file mode 100644
index 0000000..8bc9aff
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_63_formatted_canonical.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.234Z
+ false
+ false
+ 0.937500
+ 0.035156
+ true
+ 0.949219
+ 0.035156
+ 0.957031
+ 0.035156
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ 2023-07-03T10:24:17
+ 2023-07-03T10:24:17.000
+ Picasa
+
+ 2023-07-03T08:38:50.234Z
+ 2023-07-03T10:24:17.000
+ 3
+ E2727A18-83AD-4FF2-88AD-A0F8928A92FF
+ xmp.iid:CE151D84-1C9F-4732-8995-0DB221EB2D40
+ E2727A18-83AD-4FF2-88AD-A0F8928A92FF
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_63_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_63_formatted_compact.xmp
new file mode 100644
index 0000000..8eb6e79
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_63_formatted_compact.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_64.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_64.xmp
new file mode 100644
index 0000000..effd4fb
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_64.xmp
@@ -0,0 +1,144 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.266Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.266Z
+
+ 0.200000
+ true
+ 0.058594
+ 0.992188
+ 0.054688
+ 0.992188
+ 0.062500
+ 0.992188
+ 0A1CD515-96CC-4C3C-BCDC-3C149E5B1B7B
+ 0A1CD515-96CC-4C3C-BCDC-3C149E5B1B7B
+ xmp.iid:93448A36-DA57-437B-AE7D-D30F437D8587
+ 2023-07-03T10:24:16
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3789
+ 5111
+ pixel
+
+
+
+
+ Face
+ Dude
+ mylio_face
+
+ 0.434403
+ 0.501957
+ 0.048743
+ 0.044600
+ normalized
+
+
+
+
+
+
+
+ Dude
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_64_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_64_formatted_canonical.xmp
new file mode 100644
index 0000000..16e1fad
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_64_formatted_canonical.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+ Dude
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.266Z
+ false
+ false
+ 0.992188
+ 0.054688
+ true
+ 0.992188
+ 0.062500
+ 0.992188
+ 0.058594
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+ 5111
+ pixel
+ 3789
+
+
+
+
+
+
+
+
+ 0.044600
+ normalized
+ 0.048743
+ 0.434403
+ 0.501957
+
+
+ mylio_face
+ Dude
+ Face
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.266Z
+ 2023-07-03T10:24:16.000
+ 3
+ 0A1CD515-96CC-4C3C-BCDC-3C149E5B1B7B
+ xmp.iid:93448A36-DA57-437B-AE7D-D30F437D8587
+ 0A1CD515-96CC-4C3C-BCDC-3C149E5B1B7B
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_64_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_64_formatted_compact.xmp
new file mode 100644
index 0000000..40656ac
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_64_formatted_compact.xmp
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+ Dude
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_65.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_65.xmp
new file mode 100644
index 0000000..4d6a5a7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_65.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.297Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.297Z
+
+ 0.200000
+ true
+ 0.035156
+ 0.914062
+ 0.035156
+ 0.894531
+ 0.035156
+ 0.910156
+ 305022A9-2326-4E30-BA84-4BF0F4EE2527
+ 305022A9-2326-4E30-BA84-4BF0F4EE2527
+ xmp.iid:C3F9CB2A-A04C-4801-9C8B-C1570E1944C8
+ 2023-07-03T10:24:16
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_65_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_65_formatted_canonical.xmp
new file mode 100644
index 0000000..5ac932a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_65_formatted_canonical.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.297Z
+ false
+ false
+ 0.894531
+ 0.035156
+ true
+ 0.910156
+ 0.035156
+ 0.914062
+ 0.035156
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.297Z
+ 2023-07-03T10:24:16.000
+ 3
+ 305022A9-2326-4E30-BA84-4BF0F4EE2527
+ xmp.iid:C3F9CB2A-A04C-4801-9C8B-C1570E1944C8
+ 305022A9-2326-4E30-BA84-4BF0F4EE2527
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_65_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_65_formatted_compact.xmp
new file mode 100644
index 0000000..b20bf7c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_65_formatted_compact.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_66.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_66.xmp
new file mode 100644
index 0000000..2d933d4
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_66.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.328Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.328Z
+
+ 0.200000
+ true
+ 0.128906
+ 0.863281
+ 0.093750
+ 0.886719
+ 0.113281
+ 0.875000
+ A8348997-B85C-442B-8E2A-5513CE7F17B2
+ A8348997-B85C-442B-8E2A-5513CE7F17B2
+ xmp.iid:F9FA1E1B-EF9C-4709-BC2B-8504A7549E75
+ 2023-07-03T10:24:16
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_66_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_66_formatted_canonical.xmp
new file mode 100644
index 0000000..75c5997
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_66_formatted_canonical.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.328Z
+ false
+ false
+ 0.886719
+ 0.093750
+ true
+ 0.875000
+ 0.113281
+ 0.863281
+ 0.128906
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.328Z
+ 2023-07-03T10:24:16.000
+ 3
+ A8348997-B85C-442B-8E2A-5513CE7F17B2
+ xmp.iid:F9FA1E1B-EF9C-4709-BC2B-8504A7549E75
+ A8348997-B85C-442B-8E2A-5513CE7F17B2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_66_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_66_formatted_compact.xmp
new file mode 100644
index 0000000..163cce9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_66_formatted_compact.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_67.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_67.xmp
new file mode 100644
index 0000000..9eb8219
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_67.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.375Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.375Z
+
+ 0.200000
+ true
+ 0.066406
+ 0.855469
+ 0.054688
+ 0.859375
+ 0.058594
+ 0.855469
+ BF5348B9-91FE-466D-B041-A9EC224EEEA5
+ BF5348B9-91FE-466D-B041-A9EC224EEEA5
+ xmp.iid:27D90DB3-BE35-451B-9685-D8948DA2F301
+ 2023-07-03T10:24:16
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_67_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_67_formatted_canonical.xmp
new file mode 100644
index 0000000..eff5171
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_67_formatted_canonical.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.375Z
+ false
+ false
+ 0.859375
+ 0.054688
+ true
+ 0.855469
+ 0.058594
+ 0.855469
+ 0.066406
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.375Z
+ 2023-07-03T10:24:16.000
+ 3
+ BF5348B9-91FE-466D-B041-A9EC224EEEA5
+ xmp.iid:27D90DB3-BE35-451B-9685-D8948DA2F301
+ BF5348B9-91FE-466D-B041-A9EC224EEEA5
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_67_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_67_formatted_compact.xmp
new file mode 100644
index 0000000..373c428
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_67_formatted_compact.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_68.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_68.xmp
new file mode 100644
index 0000000..891b8e0
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_68.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.407Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.407Z
+
+ 0.200000
+ true
+ 0.093750
+ 0.957031
+ 0.082031
+ 0.949219
+ 0.089844
+ 0.953125
+ 99562B60-8A7D-41C0-BDDF-28AC49500972
+ 99562B60-8A7D-41C0-BDDF-28AC49500972
+ xmp.iid:0B41C246-DC12-4D89-91B4-A3B57EBEA72D
+ 2023-07-03T10:24:16
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_68_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_68_formatted_canonical.xmp
new file mode 100644
index 0000000..8b714d2
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_68_formatted_canonical.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.407Z
+ false
+ false
+ 0.949219
+ 0.082031
+ true
+ 0.953125
+ 0.089844
+ 0.957031
+ 0.093750
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.407Z
+ 2023-07-03T10:24:16.000
+ 3
+ 99562B60-8A7D-41C0-BDDF-28AC49500972
+ xmp.iid:0B41C246-DC12-4D89-91B4-A3B57EBEA72D
+ 99562B60-8A7D-41C0-BDDF-28AC49500972
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_68_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_68_formatted_compact.xmp
new file mode 100644
index 0000000..99e9ae4
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_68_formatted_compact.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_69.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_69.xmp
new file mode 100644
index 0000000..fe2ea10
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_69.xmp
@@ -0,0 +1,113 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.438Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.438Z
+
+ 0.200000
+ true
+ 0.093750
+ 0.957031
+ 0.082031
+ 0.933594
+ 0.089844
+ 0.945312
+ E719E390-C3BE-49F5-BB15-30217BB25BA2
+ E719E390-C3BE-49F5-BB15-30217BB25BA2
+ xmp.iid:3797091B-CC5D-426A-BCE8-A832B5560BE6
+ 2023-07-03T10:24:16
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_69_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_69_formatted_canonical.xmp
new file mode 100644
index 0000000..a2d7fd7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_69_formatted_canonical.xmp
@@ -0,0 +1,73 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.438Z
+ false
+ false
+ 0.933594
+ 0.082031
+ true
+ 0.945312
+ 0.089844
+ 0.957031
+ 0.093750
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.438Z
+ 2023-07-03T10:24:16.000
+ 3
+ E719E390-C3BE-49F5-BB15-30217BB25BA2
+ xmp.iid:3797091B-CC5D-426A-BCE8-A832B5560BE6
+ E719E390-C3BE-49F5-BB15-30217BB25BA2
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_69_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_69_formatted_compact.xmp
new file mode 100644
index 0000000..a3107c1
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_69_formatted_compact.xmp
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_6_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_6_formatted_canonical.xmp
new file mode 100644
index 0000000..9a20b06
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_6_formatted_canonical.xmp
@@ -0,0 +1,242 @@
+
+
+
+
+ True
+ True
+ 28-100mm F1.8-4.9
+ 34683/32768 -219328315/1073741824 52441832/1073741824 -7678158/1073741824
+ 1040/100 3710/100 18/10 49/10
+ True
+ 0
+ 0
+ 0
+ 0
+ Embedded
+ 54650A341B5B5CCAE8442D0B43A92BCE
+ 0
+ 0
+ 0
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ 0
+ +0.40
+ 0
+ 0
+ 0
+ False
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 25
+ 0
+ +1.0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.3
+ 0
+ 0
+ As Shot
+ 0
+ image/jpeg
+ 28-100mm F1.8-4.9
+ 2019-02-25T06:14:16
+ 2019-02-25T06:14:16
+ Adobe Photoshop Lightroom Classic 8.3.1 (Windows)
+ 2019-08-04T21:22:41+02:00
+ 2019-08-04T21:22:41+02:00
+ 0
+
+
+ adobe:docid:photoshop:402c59b6-9b47-284c-bf69-c29ff9849814
+ xmp.iid:5ed51343-dc41-7a4d-bbc9-d779a5a42cf5
+ F44F7718D2EE77F231D30D6F8BD43643
+
+
+ xmp.did:ce2000f3-5b24-1346-bf4e-cd945cb900a8
+
+
+
+
+ derived
+ converted from image/x-sony-arw to image/tiff
+
+
+
+
+ saved
+ /
+ xmp.iid:1b7c7f7f-f2d3-3644-beb2-46bf86b3b9f7
+ Adobe Photoshop Camera Raw 11.0 (Windows)
+ 2019-03-14T22:13:19+01:00
+
+
+
+
+ saved
+ /
+ xmp.iid:5ed51343-dc41-7a4d-bbc9-d779a5a42cf5
+ Adobe Photoshop CC 2019 (Windows)
+ 2019-03-14T22:29:25+01:00
+
+
+
+
+ derived
+ converted from image/tiff to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:ce2000f3-5b24-1346-bf4e-cd945cb900a8
+ Adobe Photoshop Lightroom Classic 8.3.1 (Windows)
+ 2019-08-04T21:22:41+02:00
+
+
+
+
+ xmp.iid:ce2000f3-5b24-1346-bf4e-cd945cb900a8
+ F44F7718D2EE77F231D30D6F8BD43643
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_6_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_6_formatted_compact.xmp
new file mode 100644
index 0000000..a9ec137
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_6_formatted_compact.xmp
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_7.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_7.xmp
new file mode 100644
index 0000000..a617238
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_7.xmp
@@ -0,0 +1,395 @@
+
+
+
+
+
+ DT 0mm F0 SAM
+
+
+
+ image/jpeg
+
+
+
+ 2019-04-17T18:11:48
+
+
+
+ 2019-04-17T18:11:48
+ Adobe Photoshop Lightroom 4.4 (Windows)
+ 2019-04-19T13:15:50+02:00
+ 2019-04-19T13:15:50+02:00
+ 0
+
+
+
+
+ 41776F425485867EA1701AE086BCD5CE
+ 41776F425485867EA1701AE086BCD5CE
+
+ xmp.did:200afdf1-ac0c-6b4c-bfdd-8f8e7a84c312
+
+
+
+ derived
+ saved to new location
+
+
+ saved
+ /
+ xmp.iid:200afdf1-ac0c-6b4c-bfdd-8f8e7a84c312
+ Adobe Photoshop Lightroom 4.4 (Windows)
+ 2019-04-19T13:15:50+02:00
+
+
+
+ xmp.iid:200afdf1-ac0c-6b4c-bfdd-8f8e7a84c312
+ 41776F425485867EA1701AE086BCD5CE
+
+
+
+ True
+ 0
+ -10
+ 0
+ 0
+ Embedded
+ 0
+ 25
+ 50
+ +10
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +0.90
+ 0
+ 0
+ 0
+ False
+ True
+ +25
+ 0
+ -15
+ -20
+ +48
+ +10
+ 0
+ +48
+ -15
+ +5
+ 0
+ 0
+ 0
+ LensDefaults
+ -20
+ -25
+ -5
+ -11
+ +10
+ 0
+ -15
+ +12
+ 0
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+
+
+ d 0.424000 0.441943
+ d 0.424485 0.441943
+ d 0.424803 0.441647
+ d 0.425220 0.441351
+ d 0.425538 0.441055
+ d 0.426023 0.441055
+ d 0.426341 0.440758
+ d 0.426827 0.440758
+ d 0.427312 0.440758
+ d 0.427797 0.440758
+ d 0.428282 0.440758
+ d 0.428767 0.440758
+ d 0.429252 0.440758
+ d 0.429668 0.441055
+ d 0.430153 0.441055
+ d 0.430638 0.441055
+ d 0.430957 0.441351
+ d 0.431442 0.441351
+ d 0.431927 0.441351
+ d 0.431667 0.441211
+ d 0.431500 0.440645
+ d 0.431132 0.440401
+ d 0.430771 0.439870
+ d 0.430467 0.439514
+ d 0.430037 0.439277
+ d 0.429621 0.438981
+ d 0.429136 0.438981
+ d 0.428650 0.438981
+ d 0.428234 0.439277
+ d 0.427833 0.439600
+ d 0.427667 0.440166
+ d 0.427251 0.440462
+ d 0.426834 0.440757
+ d 0.426419 0.441055
+ d 0.426000 0.441173
+ d 0.425679 0.441625
+ d 0.425333 0.441886
+ d 0.424881 0.441943
+ d 0.424562 0.442239
+ d 0.424077 0.442239
+ d 0.423833 0.441810
+ d 0.423833 0.440948
+ d 0.424045 0.440462
+ d 0.424363 0.440166
+ d 0.424667 0.439670
+ d 0.425069 0.439451
+ d 0.425514 0.439277
+ d 0.425999 0.439277
+ d 0.426317 0.438981
+ d 0.426802 0.438981
+ d 0.427287 0.438981
+ d 0.427772 0.438981
+ d 0.428258 0.438981
+ d 0.428743 0.438981
+ d 0.429228 0.438981
+ d 0.429650 0.439248
+ d 0.430000 0.439506
+ d 0.430414 0.439717
+
+
+ 1.000000
+ 1.000000
+ 0.001617
+ Mask/Paint
+
+
+ 0.000000
+
+
+ d 0.472500 0.444017
+ d 0.472333 0.443451
+ d 0.472167 0.443371
+ d 0.472167 0.443208
+ d 0.472167 0.442345
+ d 0.472356 0.442239
+ d 0.472500 0.442817
+ d 0.472500 0.441955
+ d 0.472645 0.441351
+ d 0.473061 0.441055
+ d 0.473546 0.441055
+ d 0.474000 0.441110
+ d 0.474183 0.441647
+ d 0.474668 0.441647
+ d 0.475153 0.441647
+ d 0.475638 0.441647
+ d 0.476087 0.441802
+ d 0.476500 0.442013
+ d 0.476858 0.442239
+ d 0.477274 0.442536
+ d 0.477759 0.442536
+ d 0.478167 0.442846
+ d 0.477840 0.443128
+ d 0.477424 0.443424
+ d 0.476957 0.443501
+ d 0.476523 0.443720
+ d 0.476107 0.444017
+ d 0.475667 0.444096
+ d 0.475303 0.444313
+ d 0.474823 0.444332
+ d 0.474402 0.444609
+ d 0.474225 0.444209
+ d 0.474500 0.443549
+ d 0.474581 0.442832
+ d 0.474900 0.442536
+ d 0.475385 0.442536
+ d 0.475667 0.442897
+ d 0.475855 0.443424
+ d 0.476241 0.443852
+ d 0.476521 0.444313
+ d 0.476829 0.444010
+ d 0.476667 0.443267
+ d 0.476379 0.442617
+ d 0.475913 0.442536
+ d 0.475428 0.442536
+ d 0.474943 0.442536
+ d 0.474667 0.442907
+ d 0.474363 0.443372
+ d 0.474057 0.443720
+ d 0.473641 0.444017
+ d 0.473492 0.443875
+ d 0.473790 0.443424
+ d 0.474275 0.443424
+ d 0.474733 0.443542
+ d 0.475167 0.443738
+ d 0.475495 0.444017
+ d 0.475980 0.444017
+ d 0.476465 0.444017
+ d 0.476950 0.444017
+ d 0.477366 0.443720
+ d 0.477851 0.443720
+ d 0.478336 0.443720
+ d 0.478667 0.443445
+
+
+ 1.000000
+ 1.000000
+ 0.001617
+ Mask/Paint
+
+
+
+ 0.000000
+ 0.000000
+ 0.050000
+ 0.000000
+ 0.000000
+ 1.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.126440
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 6.7
+ 0
+ 0
+ -10
+ +25
+ -20
+ -41
+ -10
+ +10
+ -51
+ +20
+ 0
+ 0
+ -5
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ Custom
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 18
+ 26, 28
+ 51, 41
+ 77, 66
+ 102, 100
+ 128, 135
+ 153, 169
+ 179, 194
+ 204, 216
+ 230, 232
+ 255, 247
+
+
+
+
+ 0, 18
+ 26, 25
+ 51, 36
+ 77, 57
+ 102, 92
+ 128, 132
+ 153, 166
+ 179, 193
+ 204, 215
+ 230, 231
+ 255, 246
+
+
+
+
+ 0, 18
+ 26, 23
+ 51, 33
+ 77, 53
+ 102, 88
+ 128, 132
+ 153, 168
+ 179, 195
+ 204, 216
+ 230, 232
+ 255, 247
+
+
+ 7.4
+ 0
+ 0
+ Custom
+ +20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_70.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_70.xmp
new file mode 100644
index 0000000..663049b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_70.xmp
@@ -0,0 +1,113 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.469Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.469Z
+
+ 0.200000
+ true
+ 0.058594
+ 0.972656
+ 0.078125
+ 0.972656
+ 0.066406
+ 0.972656
+ C56D742E-7E66-4DD0-A7A8-BF1BF90829DC
+ C56D742E-7E66-4DD0-A7A8-BF1BF90829DC
+ xmp.iid:9A9DCCEF-9787-4328-A99C-BF7F64CD1707
+ 2023-07-03T10:24:16
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_70_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_70_formatted_canonical.xmp
new file mode 100644
index 0000000..151158e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_70_formatted_canonical.xmp
@@ -0,0 +1,73 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.469Z
+ false
+ false
+ 0.972656
+ 0.078125
+ true
+ 0.972656
+ 0.066406
+ 0.972656
+ 0.058594
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.469Z
+ 2023-07-03T10:24:16.000
+ 3
+ C56D742E-7E66-4DD0-A7A8-BF1BF90829DC
+ xmp.iid:9A9DCCEF-9787-4328-A99C-BF7F64CD1707
+ C56D742E-7E66-4DD0-A7A8-BF1BF90829DC
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_70_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_70_formatted_compact.xmp
new file mode 100644
index 0000000..e77cd11
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_70_formatted_compact.xmp
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_71.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_71.xmp
new file mode 100644
index 0000000..7f9e4eb
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_71.xmp
@@ -0,0 +1,113 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.627Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.627Z
+
+ 0.200000
+ true
+ 0.027344
+ 0.984375
+ 0.015625
+ 0.992188
+ 0.031250
+ 0.992188
+ 6D2862A7-B66C-449B-929D-4826166F1C05
+ 6D2862A7-B66C-449B-929D-4826166F1C05
+ xmp.iid:5D069A16-302F-447F-B6AF-54880F1BB808
+ 2023-07-03T10:24:16
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_71_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_71_formatted_canonical.xmp
new file mode 100644
index 0000000..7b893b3
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_71_formatted_canonical.xmp
@@ -0,0 +1,73 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.627Z
+ false
+ false
+ 0.992188
+ 0.015625
+ true
+ 0.992188
+ 0.031250
+ 0.984375
+ 0.027344
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.627Z
+ 2023-07-03T10:24:16.000
+ 3
+ 6D2862A7-B66C-449B-929D-4826166F1C05
+ xmp.iid:5D069A16-302F-447F-B6AF-54880F1BB808
+ 6D2862A7-B66C-449B-929D-4826166F1C05
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_71_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_71_formatted_compact.xmp
new file mode 100644
index 0000000..3da341a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_71_formatted_compact.xmp
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_72.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_72.xmp
new file mode 100644
index 0000000..7d18bbd
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_72.xmp
@@ -0,0 +1,112 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.659Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.659Z
+
+ 0.200000
+ true
+ 0.062500
+ 0.984375
+ 0.062500
+ 0.984375
+ 0.082031
+ 0.980469
+ 02BFE53A-4632-4B6C-898E-E60D01CB863E
+ 02BFE53A-4632-4B6C-898E-E60D01CB863E
+ xmp.iid:2E19D621-C348-4A40-95B4-98BD5C878293
+ 2023-07-03T10:24:16
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_72_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_72_formatted_canonical.xmp
new file mode 100644
index 0000000..072018a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_72_formatted_canonical.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.659Z
+ false
+ false
+ 0.984375
+ 0.062500
+ true
+ 0.980469
+ 0.082031
+ 0.984375
+ 0.062500
+ 1
+ 0.200000
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.659Z
+ 2023-07-03T10:24:16.000
+ 3
+ 02BFE53A-4632-4B6C-898E-E60D01CB863E
+ xmp.iid:2E19D621-C348-4A40-95B4-98BD5C878293
+ 02BFE53A-4632-4B6C-898E-E60D01CB863E
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_72_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_72_formatted_compact.xmp
new file mode 100644
index 0000000..a8c7f7a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_72_formatted_compact.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_73.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_73.xmp
new file mode 100644
index 0000000..7bf826a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_73.xmp
@@ -0,0 +1,104 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.690Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.690Z
+
+ 95FBE155-F7AE-40C3-99FA-5B458EB9E4E9
+ 95FBE155-F7AE-40C3-99FA-5B458EB9E4E9
+ xmp.iid:9E0EB322-7845-4C92-A58A-A44330B7A3D7
+ 2023-07-03T10:24:16
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_73_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_73_formatted_canonical.xmp
new file mode 100644
index 0000000..97af608
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_73_formatted_canonical.xmp
@@ -0,0 +1,64 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.690Z
+ false
+ false
+ 1
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.690Z
+ 2023-07-03T10:24:16.000
+ 3
+ 95FBE155-F7AE-40C3-99FA-5B458EB9E4E9
+ xmp.iid:9E0EB322-7845-4C92-A58A-A44330B7A3D7
+ 95FBE155-F7AE-40C3-99FA-5B458EB9E4E9
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_73_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_73_formatted_compact.xmp
new file mode 100644
index 0000000..61984f7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_73_formatted_compact.xmp
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_74.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_74.xmp
new file mode 100644
index 0000000..d1d49c5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_74.xmp
@@ -0,0 +1,104 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.831Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.831Z
+
+ E8635A6B-1EFA-4AFA-99D1-B44BD5E256E4
+ E8635A6B-1EFA-4AFA-99D1-B44BD5E256E4
+ xmp.iid:7D0E6B34-AF61-468E-9B1E-92261F78617F
+ 2023-07-03T10:24:16
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_74_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_74_formatted_canonical.xmp
new file mode 100644
index 0000000..d8f2a8c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_74_formatted_canonical.xmp
@@ -0,0 +1,64 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.831Z
+ false
+ false
+ 1
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.831Z
+ 2023-07-03T10:24:16.000
+ 3
+ E8635A6B-1EFA-4AFA-99D1-B44BD5E256E4
+ xmp.iid:7D0E6B34-AF61-468E-9B1E-92261F78617F
+ E8635A6B-1EFA-4AFA-99D1-B44BD5E256E4
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_74_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_74_formatted_compact.xmp
new file mode 100644
index 0000000..41cae81
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_74_formatted_compact.xmp
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_75.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_75.xmp
new file mode 100644
index 0000000..712fdb7
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_75.xmp
@@ -0,0 +1,136 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+ Picasa
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.863Z
+ 3
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T10:24:17.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.863Z
+
+ AFF04CE0-9D36-4191-9384-CAF6DF36B843
+ AFF04CE0-9D36-4191-9384-CAF6DF36B843
+ xmp.iid:CAD42AE2-4C5B-4E61-8726-7746EE7C4836
+ 2023-07-03T10:24:17
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 6240
+ 4160
+ pixel
+
+
+
+
+ Face
+ Guy
+ mylio_face
+
+ 0.553701
+ 0.251482
+ 0.034140
+ 0.098843
+ normalized
+
+
+
+
+
+
+
+ Guy
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_75_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_75_formatted_canonical.xmp
new file mode 100644
index 0000000..ed3a1cb
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_75_formatted_canonical.xmp
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+ Guy
+
+
+ 2023-07-03T10:24:17.000
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.863Z
+ false
+ false
+ 1
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+ 4160
+ pixel
+ 6240
+
+
+
+
+
+
+
+
+ 0.098843
+ normalized
+ 0.034140
+ 0.553701
+ 0.251482
+
+
+ mylio_face
+ Guy
+ Face
+
+
+
+
+
+
+ 2023-07-03T10:24:17
+ 2023-07-03T10:24:17.000
+ Picasa
+
+ 2023-07-03T08:38:50.863Z
+ 2023-07-03T10:24:17.000
+ 3
+ AFF04CE0-9D36-4191-9384-CAF6DF36B843
+ xmp.iid:CAD42AE2-4C5B-4E61-8726-7746EE7C4836
+ AFF04CE0-9D36-4191-9384-CAF6DF36B843
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_75_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_75_formatted_compact.xmp
new file mode 100644
index 0000000..a39aa8f
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_75_formatted_compact.xmp
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+ Guy
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_76.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_76.xmp
new file mode 100644
index 0000000..d2e0f1c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_76.xmp
@@ -0,0 +1,104 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+ Picasa
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.909Z
+ 3
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T10:24:17.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.909Z
+
+ 06609180-665A-4101-9405-4A1C7F01B33B
+ 06609180-665A-4101-9405-4A1C7F01B33B
+ xmp.iid:65E7CC59-75F2-4A23-911C-42B7B6BC501C
+ 2023-07-03T10:24:17
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_76_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_76_formatted_canonical.xmp
new file mode 100644
index 0000000..dce3e6b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_76_formatted_canonical.xmp
@@ -0,0 +1,64 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:50.909Z
+ false
+ false
+ 1
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+ 2023-07-03T10:24:17
+ 2023-07-03T10:24:17.000
+ Picasa
+
+ 2023-07-03T08:38:50.909Z
+ 2023-07-03T10:24:17.000
+ 3
+ 06609180-665A-4101-9405-4A1C7F01B33B
+ xmp.iid:65E7CC59-75F2-4A23-911C-42B7B6BC501C
+ 06609180-665A-4101-9405-4A1C7F01B33B
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_76_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_76_formatted_compact.xmp
new file mode 100644
index 0000000..9e3c3f5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_76_formatted_compact.xmp
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_77.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_77.xmp
new file mode 100644
index 0000000..606a013
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_77.xmp
@@ -0,0 +1,136 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Picasa
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.925Z
+ 3
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T10:24:16.000
+ false
+ false
+ 1
+ 2023-07-03T08:38:50.925Z
+
+ 913633B8-51B6-4D64-AEB1-7FF437021B1C
+ 913633B8-51B6-4D64-AEB1-7FF437021B1C
+ xmp.iid:EDB9E80D-11B1-444F-A68F-DE6C99A70C55
+ 2023-07-03T10:24:16
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2971
+ 3961
+ pixel
+
+
+
+
+ Face
+ AnotherGirl
+ mylio_face
+
+ 0.247770
+ 0.515958
+ 0.149660
+ 0.150897
+ normalized
+
+
+
+
+
+
+
+ AnotherGirl
+
+
+
+
+
+ 3
+
+ False
+
+
+ acdsee
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_77_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_77_formatted_canonical.xmp
new file mode 100644
index 0000000..d523ace
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_77_formatted_canonical.xmp
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+ AnotherGirl
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.925Z
+ false
+ false
+ 1
+
+
+ <Categories><Category Assigned="1">Verschiedenes</Category></Categories>
+
+
+
+
+ acdsee
+
+
+
+ 3
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+ 3961
+ pixel
+ 2971
+
+
+
+
+
+
+
+
+ 0.150897
+ normalized
+ 0.149660
+ 0.247770
+ 0.515958
+
+
+ mylio_face
+ AnotherGirl
+ Face
+
+
+
+
+
+
+ 2023-07-03T10:24:16
+ 2023-07-03T10:24:16.000
+ Picasa
+
+ 2023-07-03T08:38:50.925Z
+ 2023-07-03T10:24:16.000
+ 3
+ 913633B8-51B6-4D64-AEB1-7FF437021B1C
+ xmp.iid:EDB9E80D-11B1-444F-A68F-DE6C99A70C55
+ 913633B8-51B6-4D64-AEB1-7FF437021B1C
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_77_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_77_formatted_compact.xmp
new file mode 100644
index 0000000..0077262
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_77_formatted_compact.xmp
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+ AnotherGirl
+
+
+
+
+ acdsee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_78.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_78.xmp
new file mode 100644
index 0000000..7b1ffe6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_78.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+ äußerst
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_78_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_78_formatted_canonical.xmp
new file mode 100644
index 0000000..e4dbef9
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_78_formatted_canonical.xmp
@@ -0,0 +1,39 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.957Z
+ false
+ false
+ 1
+
+
+ äußerst
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.957Z
+ 2023-07-03T10:24:16.000
+ 3
+ 89F4CC51-80AC-4A15-A6C4-2E37FF30958B
+ xmp.iid:6DC0E2FA-7E2F-4562-B158-C3094E70230F
+ 89F4CC51-80AC-4A15-A6C4-2E37FF30958B
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_78_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_78_formatted_compact.xmp
new file mode 100644
index 0000000..c0e3886
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_78_formatted_compact.xmp
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_79.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_79.xmp
new file mode 100644
index 0000000..c70249a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_79.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+ äußerst
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_79_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_79_formatted_canonical.xmp
new file mode 100644
index 0000000..d64462d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_79_formatted_canonical.xmp
@@ -0,0 +1,39 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.988Z
+ false
+ false
+ 1
+
+
+ äußerst
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:50.988Z
+ 2023-07-03T10:24:16.000
+ 3
+ B2182FBC-11A3-44FC-BAB8-7E8DAD2F9944
+ xmp.iid:37EE8945-4C2E-4883-AB81-D929E3A3EA73
+ B2182FBC-11A3-44FC-BAB8-7E8DAD2F9944
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_79_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_79_formatted_compact.xmp
new file mode 100644
index 0000000..cf2c448
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_79_formatted_compact.xmp
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ äußerst
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_7_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_7_formatted_canonical.xmp
new file mode 100644
index 0000000..78783b8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_7_formatted_canonical.xmp
@@ -0,0 +1,367 @@
+
+
+
+
+ DT 0mm F0 SAM
+ True
+ 0
+ -10
+ 0
+ 0
+ Embedded
+ 0
+ 25
+ 50
+ +10
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +0.90
+ 0
+ 0
+ 0
+ False
+ True
+ +25
+ 0
+ -15
+ -20
+ +48
+ +10
+ 0
+ +48
+ -15
+ +5
+ 0
+ 0
+ 0
+ LensDefaults
+ -20
+ -25
+ -5
+ -11
+ +10
+ 0
+ -15
+ +12
+ 0
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+
+
+ d 0.424000 0.441943
+ d 0.424485 0.441943
+ d 0.424803 0.441647
+ d 0.425220 0.441351
+ d 0.425538 0.441055
+ d 0.426023 0.441055
+ d 0.426341 0.440758
+ d 0.426827 0.440758
+ d 0.427312 0.440758
+ d 0.427797 0.440758
+ d 0.428282 0.440758
+ d 0.428767 0.440758
+ d 0.429252 0.440758
+ d 0.429668 0.441055
+ d 0.430153 0.441055
+ d 0.430638 0.441055
+ d 0.430957 0.441351
+ d 0.431442 0.441351
+ d 0.431927 0.441351
+ d 0.431667 0.441211
+ d 0.431500 0.440645
+ d 0.431132 0.440401
+ d 0.430771 0.439870
+ d 0.430467 0.439514
+ d 0.430037 0.439277
+ d 0.429621 0.438981
+ d 0.429136 0.438981
+ d 0.428650 0.438981
+ d 0.428234 0.439277
+ d 0.427833 0.439600
+ d 0.427667 0.440166
+ d 0.427251 0.440462
+ d 0.426834 0.440757
+ d 0.426419 0.441055
+ d 0.426000 0.441173
+ d 0.425679 0.441625
+ d 0.425333 0.441886
+ d 0.424881 0.441943
+ d 0.424562 0.442239
+ d 0.424077 0.442239
+ d 0.423833 0.441810
+ d 0.423833 0.440948
+ d 0.424045 0.440462
+ d 0.424363 0.440166
+ d 0.424667 0.439670
+ d 0.425069 0.439451
+ d 0.425514 0.439277
+ d 0.425999 0.439277
+ d 0.426317 0.438981
+ d 0.426802 0.438981
+ d 0.427287 0.438981
+ d 0.427772 0.438981
+ d 0.428258 0.438981
+ d 0.428743 0.438981
+ d 0.429228 0.438981
+ d 0.429650 0.439248
+ d 0.430000 0.439506
+ d 0.430414 0.439717
+
+
+ 1.000000
+ 1.000000
+ 0.001617
+ Mask/Paint
+
+
+
+
+ 0.000000
+
+
+ d 0.472500 0.444017
+ d 0.472333 0.443451
+ d 0.472167 0.443371
+ d 0.472167 0.443208
+ d 0.472167 0.442345
+ d 0.472356 0.442239
+ d 0.472500 0.442817
+ d 0.472500 0.441955
+ d 0.472645 0.441351
+ d 0.473061 0.441055
+ d 0.473546 0.441055
+ d 0.474000 0.441110
+ d 0.474183 0.441647
+ d 0.474668 0.441647
+ d 0.475153 0.441647
+ d 0.475638 0.441647
+ d 0.476087 0.441802
+ d 0.476500 0.442013
+ d 0.476858 0.442239
+ d 0.477274 0.442536
+ d 0.477759 0.442536
+ d 0.478167 0.442846
+ d 0.477840 0.443128
+ d 0.477424 0.443424
+ d 0.476957 0.443501
+ d 0.476523 0.443720
+ d 0.476107 0.444017
+ d 0.475667 0.444096
+ d 0.475303 0.444313
+ d 0.474823 0.444332
+ d 0.474402 0.444609
+ d 0.474225 0.444209
+ d 0.474500 0.443549
+ d 0.474581 0.442832
+ d 0.474900 0.442536
+ d 0.475385 0.442536
+ d 0.475667 0.442897
+ d 0.475855 0.443424
+ d 0.476241 0.443852
+ d 0.476521 0.444313
+ d 0.476829 0.444010
+ d 0.476667 0.443267
+ d 0.476379 0.442617
+ d 0.475913 0.442536
+ d 0.475428 0.442536
+ d 0.474943 0.442536
+ d 0.474667 0.442907
+ d 0.474363 0.443372
+ d 0.474057 0.443720
+ d 0.473641 0.444017
+ d 0.473492 0.443875
+ d 0.473790 0.443424
+ d 0.474275 0.443424
+ d 0.474733 0.443542
+ d 0.475167 0.443738
+ d 0.475495 0.444017
+ d 0.475980 0.444017
+ d 0.476465 0.444017
+ d 0.476950 0.444017
+ d 0.477366 0.443720
+ d 0.477851 0.443720
+ d 0.478336 0.443720
+ d 0.478667 0.443445
+
+
+ 1.000000
+ 1.000000
+ 0.001617
+ Mask/Paint
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.050000
+ 0.000000
+ 0.000000
+ 1.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.126440
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 6.7
+ 0
+ 0
+ -10
+ +25
+ -20
+ -41
+ -10
+ +10
+ -51
+ +20
+ 0
+ 0
+ -5
+ 25
+ 0
+ +1.0
+ 25
+ 0
+ 0
+ 0
+ 0
+ 0
+ Custom
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 18
+ 26, 28
+ 51, 41
+ 77, 66
+ 102, 100
+ 128, 135
+ 153, 169
+ 179, 194
+ 204, 216
+ 230, 232
+ 255, 247
+
+
+
+
+ 0, 18
+ 26, 25
+ 51, 36
+ 77, 57
+ 102, 92
+ 128, 132
+ 153, 166
+ 179, 193
+ 204, 215
+ 230, 231
+ 255, 246
+
+
+
+
+ 0, 18
+ 26, 23
+ 51, 33
+ 77, 53
+ 102, 88
+ 128, 132
+ 153, 168
+ 179, 195
+ 204, 216
+ 230, 232
+ 255, 247
+
+
+ 7.4
+ 0
+ 0
+ Custom
+ +20
+ image/jpeg
+ 2019-04-17T18:11:48
+ 2019-04-17T18:11:48
+ Adobe Photoshop Lightroom 4.4 (Windows)
+ 2019-04-19T13:15:50+02:00
+ 2019-04-19T13:15:50+02:00
+ 0
+
+
+ 41776F425485867EA1701AE086BCD5CE
+ 41776F425485867EA1701AE086BCD5CE
+
+
+ xmp.did:200afdf1-ac0c-6b4c-bfdd-8f8e7a84c312
+
+
+
+
+ derived
+ saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:200afdf1-ac0c-6b4c-bfdd-8f8e7a84c312
+ Adobe Photoshop Lightroom 4.4 (Windows)
+ 2019-04-19T13:15:50+02:00
+
+
+
+
+ xmp.iid:200afdf1-ac0c-6b4c-bfdd-8f8e7a84c312
+ 41776F425485867EA1701AE086BCD5CE
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_7_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_7_formatted_compact.xmp
new file mode 100644
index 0000000..b80fdd1
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_7_formatted_compact.xmp
@@ -0,0 +1,358 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ d 0.424000 0.441943
+ d 0.424485 0.441943
+ d 0.424803 0.441647
+ d 0.425220 0.441351
+ d 0.425538 0.441055
+ d 0.426023 0.441055
+ d 0.426341 0.440758
+ d 0.426827 0.440758
+ d 0.427312 0.440758
+ d 0.427797 0.440758
+ d 0.428282 0.440758
+ d 0.428767 0.440758
+ d 0.429252 0.440758
+ d 0.429668 0.441055
+ d 0.430153 0.441055
+ d 0.430638 0.441055
+ d 0.430957 0.441351
+ d 0.431442 0.441351
+ d 0.431927 0.441351
+ d 0.431667 0.441211
+ d 0.431500 0.440645
+ d 0.431132 0.440401
+ d 0.430771 0.439870
+ d 0.430467 0.439514
+ d 0.430037 0.439277
+ d 0.429621 0.438981
+ d 0.429136 0.438981
+ d 0.428650 0.438981
+ d 0.428234 0.439277
+ d 0.427833 0.439600
+ d 0.427667 0.440166
+ d 0.427251 0.440462
+ d 0.426834 0.440757
+ d 0.426419 0.441055
+ d 0.426000 0.441173
+ d 0.425679 0.441625
+ d 0.425333 0.441886
+ d 0.424881 0.441943
+ d 0.424562 0.442239
+ d 0.424077 0.442239
+ d 0.423833 0.441810
+ d 0.423833 0.440948
+ d 0.424045 0.440462
+ d 0.424363 0.440166
+ d 0.424667 0.439670
+ d 0.425069 0.439451
+ d 0.425514 0.439277
+ d 0.425999 0.439277
+ d 0.426317 0.438981
+ d 0.426802 0.438981
+ d 0.427287 0.438981
+ d 0.427772 0.438981
+ d 0.428258 0.438981
+ d 0.428743 0.438981
+ d 0.429228 0.438981
+ d 0.429650 0.439248
+ d 0.430000 0.439506
+ d 0.430414 0.439717
+
+
+
+
+
+
+
+
+ d 0.472500 0.444017
+ d 0.472333 0.443451
+ d 0.472167 0.443371
+ d 0.472167 0.443208
+ d 0.472167 0.442345
+ d 0.472356 0.442239
+ d 0.472500 0.442817
+ d 0.472500 0.441955
+ d 0.472645 0.441351
+ d 0.473061 0.441055
+ d 0.473546 0.441055
+ d 0.474000 0.441110
+ d 0.474183 0.441647
+ d 0.474668 0.441647
+ d 0.475153 0.441647
+ d 0.475638 0.441647
+ d 0.476087 0.441802
+ d 0.476500 0.442013
+ d 0.476858 0.442239
+ d 0.477274 0.442536
+ d 0.477759 0.442536
+ d 0.478167 0.442846
+ d 0.477840 0.443128
+ d 0.477424 0.443424
+ d 0.476957 0.443501
+ d 0.476523 0.443720
+ d 0.476107 0.444017
+ d 0.475667 0.444096
+ d 0.475303 0.444313
+ d 0.474823 0.444332
+ d 0.474402 0.444609
+ d 0.474225 0.444209
+ d 0.474500 0.443549
+ d 0.474581 0.442832
+ d 0.474900 0.442536
+ d 0.475385 0.442536
+ d 0.475667 0.442897
+ d 0.475855 0.443424
+ d 0.476241 0.443852
+ d 0.476521 0.444313
+ d 0.476829 0.444010
+ d 0.476667 0.443267
+ d 0.476379 0.442617
+ d 0.475913 0.442536
+ d 0.475428 0.442536
+ d 0.474943 0.442536
+ d 0.474667 0.442907
+ d 0.474363 0.443372
+ d 0.474057 0.443720
+ d 0.473641 0.444017
+ d 0.473492 0.443875
+ d 0.473790 0.443424
+ d 0.474275 0.443424
+ d 0.474733 0.443542
+ d 0.475167 0.443738
+ d 0.475495 0.444017
+ d 0.475980 0.444017
+ d 0.476465 0.444017
+ d 0.476950 0.444017
+ d 0.477366 0.443720
+ d 0.477851 0.443720
+ d 0.478336 0.443720
+ d 0.478667 0.443445
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 18
+ 26, 28
+ 51, 41
+ 77, 66
+ 102, 100
+ 128, 135
+ 153, 169
+ 179, 194
+ 204, 216
+ 230, 232
+ 255, 247
+
+
+
+
+ 0, 18
+ 26, 25
+ 51, 36
+ 77, 57
+ 102, 92
+ 128, 132
+ 153, 166
+ 179, 193
+ 204, 215
+ 230, 231
+ 255, 246
+
+
+
+
+ 0, 18
+ 26, 23
+ 51, 33
+ 77, 53
+ 102, 88
+ 128, 132
+ 153, 168
+ 179, 195
+ 204, 216
+ 230, 232
+ 255, 247
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_8.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_8.xmp
new file mode 100644
index 0000000..3954013
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_8.xmp
@@ -0,0 +1,287 @@
+
+
+
+
+
+ 72/100
+ 1.1.4
+ 0/1
+ 0
+ 35mm
+ 169
+ 35/1 35/1 0/0 0/0
+ 0000000000
+ 252020002303
+
+
+
+ image/jpeg
+
+
+
+ 2019-02-16T06:37:31.93
+
+
+
+ 2019-02-16T06:37:31.93
+ Adobe Photoshop Lightroom 5.7.1 (Macintosh)
+ 2019-02-16T19:26-05:00
+ 2019-02-16T19:26:00-05:00
+ 3
+
+
+
+
+ 7AE2C6EAF539AF535316F1B6DBCC874C
+ 7AE2C6EAF539AF535316F1B6DBCC874C
+
+ xmp.did:04e69b56-089f-4b0b-a69d-08111a57f914
+
+
+
+ derived
+ saved to new location
+
+
+ saved
+ /
+ xmp.iid:04e69b56-089f-4b0b-a69d-08111a57f914
+ Adobe Photoshop Lightroom 5.7.1 (Macintosh)
+ 2019-02-16T19:26-05:00
+
+
+
+ xmp.iid:04e69b56-089f-4b0b-a69d-08111a57f914
+ 7AE2C6EAF539AF535316F1B6DBCC874C
+
+
+
+ True
+ 0
+ 134348800
+ +25
+ 0
+ 0
+ Embedded
+ +11
+ 25
+ 50
+ 50
+ +6
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +0.25
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.083913
+ 0.268014
+ 1.000000
+ Mask/Gradient
+ 0.373893
+ 0.266914
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.224449
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+ 16
+ 45
+ 3872006559
+ 40
+ 0
+ 0
+ False
+ True
+ -20
+ +28
+ -15
+ +8
+ +15
+ -8
+ -3
+ -6
+ -5
+ +2
+ -7
+ 0
+ 0
+ LensDefaults
+ +4
+ -31
+ -15
+ +30
+ -6
+ +30
+ +5
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0
+ 6.7
+ 0
+ 0
+ -4
+ -33
+ -30
+ -50
+ -5
+ -7
+ -25
+ -2
+ -20
+ 0
+ +20
+ 25
+ 0
+ +1.0
+ 51
+ +98
+ 79
+ 5
+ 74
+ 4
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 1, 2
+ 50, 47
+ 123, 131
+ 192, 189
+ 255, 255
+
+
+
+
+ 0, 26
+ 34, 38
+ 92, 88
+ 163, 177
+ 222, 233
+ 255, 255
+
+
+
+
+ 0, 14
+ 34, 27
+ 90, 88
+ 163, 180
+ 255, 255
+
+
+
+
+ 0, 6
+ 37, 26
+ 90, 88
+ 161, 182
+ 215, 228
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 8.7.1
+ -2
+ 0
+ Custom
+ -14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_80.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_80.xmp
new file mode 100644
index 0000000..d1b60ee
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_80.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_80_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_80_formatted_canonical.xmp
new file mode 100644
index 0000000..cf1bfea
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_80_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:15.000
+
+ 2023-07-03T10:24:15.000
+ 2023-07-03T08:38:51.035Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:15.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:15.000
+ 2023-07-03T08:38:51.035Z
+ 2023-07-03T10:24:15.000
+ 3
+ E1C0AEC6-1CC7-4654-B211-1F5CCB9E62CD
+ xmp.iid:5FAFB468-43CB-48A2-A89C-A5B7A6404CF7
+ E1C0AEC6-1CC7-4654-B211-1F5CCB9E62CD
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_80_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_80_formatted_compact.xmp
new file mode 100644
index 0000000..94f55d8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_80_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_81.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_81.xmp
new file mode 100644
index 0000000..615d87a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_81.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_81_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_81_formatted_canonical.xmp
new file mode 100644
index 0000000..b9e9e6d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_81_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:15.000
+
+ 2023-07-03T10:24:15.000
+ 2023-07-03T08:38:51.051Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:15.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:15.000
+ 2023-07-03T08:38:51.051Z
+ 2023-07-03T10:24:15.000
+ AA3E9DA6-AFE1-4E54-AC09-60B8FD8A5263
+ xmp.iid:A886C678-5DBB-4BBF-A2F9-5448D92C7942
+ AA3E9DA6-AFE1-4E54-AC09-60B8FD8A5263
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_81_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_81_formatted_compact.xmp
new file mode 100644
index 0000000..b1b940c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_81_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_82.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_82.xmp
new file mode 100644
index 0000000..b25d696
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_82.xmp
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flower
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_82_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_82_formatted_canonical.xmp
new file mode 100644
index 0000000..f7a1bdf
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_82_formatted_canonical.xmp
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+ Flower
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.082Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+
+
+
+
+ 4999
+ pixel
+ 4000
+
+
+
+
+
+
+
+
+ 0.078798
+ normalized
+ 0.080316
+ 0.125439
+ 0.624049
+
+
+ mylio_face
+ Flower
+ Face
+
+
+
+
+
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.082Z
+ 2023-07-03T10:24:16.000
+ 1BF85008-4B8E-4380-B4FC-ECCD6D69E75D
+ xmp.iid:CC2C9D18-1092-413C-9243-516B9D056980
+ 1BF85008-4B8E-4380-B4FC-ECCD6D69E75D
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_82_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_82_formatted_compact.xmp
new file mode 100644
index 0000000..881bcdc
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_82_formatted_compact.xmp
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+ Flower
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_83.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_83.xmp
new file mode 100644
index 0000000..cec942c
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_83.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_83_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_83_formatted_canonical.xmp
new file mode 100644
index 0000000..ba10068
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_83_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.113Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.113Z
+ 2023-07-03T10:24:16.000
+ 881404E3-0859-49FD-A274-289E96E96045
+ xmp.iid:22F5DC7D-C14C-4F72-B6B3-1E3E4499FE2D
+ 881404E3-0859-49FD-A274-289E96E96045
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_83_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_83_formatted_compact.xmp
new file mode 100644
index 0000000..e0d191a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_83_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_84.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_84.xmp
new file mode 100644
index 0000000..4bd26a5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_84.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_84_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_84_formatted_canonical.xmp
new file mode 100644
index 0000000..ad17f54
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_84_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.145Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.145Z
+ 2023-07-03T10:24:16.000
+ F7CC623B-35FB-4501-AC19-562CC8DD1E1F
+ xmp.iid:E0F275CB-0ED4-444F-9B14-FD430107B965
+ F7CC623B-35FB-4501-AC19-562CC8DD1E1F
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_84_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_84_formatted_compact.xmp
new file mode 100644
index 0000000..b9bc7c8
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_84_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_85.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_85.xmp
new file mode 100644
index 0000000..b9ea3fc
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_85.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_85_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_85_formatted_canonical.xmp
new file mode 100644
index 0000000..1b8babb
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_85_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.192Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.192Z
+ 2023-07-03T10:24:16.000
+ 3329C280-DBBA-4775-BFE2-3766C7B03356
+ xmp.iid:F3943C78-E1BF-4DF0-8042-A5D06CBE46E0
+ 3329C280-DBBA-4775-BFE2-3766C7B03356
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_85_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_85_formatted_compact.xmp
new file mode 100644
index 0000000..5281d3d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_85_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_86.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_86.xmp
new file mode 100644
index 0000000..95b3e07
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_86.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_86_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_86_formatted_canonical.xmp
new file mode 100644
index 0000000..f3b2f7d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_86_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.254Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.254Z
+ 2023-07-03T10:24:16.000
+ FBB64F64-3477-4EA3-89D0-40DBEB0CBA2A
+ xmp.iid:A094A714-C87F-4763-9151-2C92DB9D3739
+ FBB64F64-3477-4EA3-89D0-40DBEB0CBA2A
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_86_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_86_formatted_compact.xmp
new file mode 100644
index 0000000..54adab6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_86_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_87.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_87.xmp
new file mode 100644
index 0000000..ebc5885
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_87.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_87_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_87_formatted_canonical.xmp
new file mode 100644
index 0000000..44fdd4e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_87_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.301Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.301Z
+ 2023-07-03T10:24:16.000
+ A9F1CDA2-D914-4960-BD26-73FC7C3BDA54
+ xmp.iid:51E206EB-C711-457F-97CA-612DC0F8E363
+ A9F1CDA2-D914-4960-BD26-73FC7C3BDA54
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_87_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_87_formatted_compact.xmp
new file mode 100644
index 0000000..8e85276
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_87_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_88.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_88.xmp
new file mode 100644
index 0000000..a0e6264
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_88.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_88_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_88_formatted_canonical.xmp
new file mode 100644
index 0000000..6cf5344
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_88_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.339Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.339Z
+ 2023-07-03T10:24:16.000
+ C36E7ECA-0F77-4189-B037-349D24B60D6D
+ xmp.iid:7459085C-4BD7-48C2-B92D-0D62132130B3
+ C36E7ECA-0F77-4189-B037-349D24B60D6D
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_88_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_88_formatted_compact.xmp
new file mode 100644
index 0000000..0389aac
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_88_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_89.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_89.xmp
new file mode 100644
index 0000000..7fc430b
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_89.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_89_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_89_formatted_canonical.xmp
new file mode 100644
index 0000000..34ffe48
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_89_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.395Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.395Z
+ 2023-07-03T10:24:16.000
+ 1EE4D8AB-7F24-487C-87FF-92712FC646F5
+ xmp.iid:1C1F6F03-6452-4420-9F9B-95B2FD1CDF3C
+ 1EE4D8AB-7F24-487C-87FF-92712FC646F5
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_89_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_89_formatted_compact.xmp
new file mode 100644
index 0000000..b2724d2
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_89_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_8_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_8_formatted_canonical.xmp
new file mode 100644
index 0000000..0e3b066
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_8_formatted_canonical.xmp
@@ -0,0 +1,257 @@
+
+
+
+
+ 72/100
+ 1.1.4
+ 0/1
+ 0
+ 35mm
+ 169
+ 35/1 35/1 0/0 0/0
+ 0000000000
+ 252020002303
+ True
+ 0
+ 134348800
+ +25
+ 0
+ 0
+ Embedded
+ +11
+ 25
+ 50
+ 50
+ +6
+ False
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +0.25
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.083913
+ 0.268014
+ 1.000000
+ Mask/Gradient
+ 0.373893
+ 0.266914
+
+
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.224449
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+ Correction
+
+
+
+
+ 16
+ 45
+ 3872006559
+ 40
+ 0
+ 0
+ False
+ True
+ -20
+ +28
+ -15
+ +8
+ +15
+ -8
+ -3
+ -6
+ -5
+ +2
+ -7
+ 0
+ 0
+ LensDefaults
+ +4
+ -31
+ -15
+ +30
+ -6
+ +30
+ +5
+ 0
+ 0
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0
+ 6.7
+ 0
+ 0
+ -4
+ -33
+ -30
+ -50
+ -5
+ -7
+ -25
+ -2
+ -20
+ 0
+ +20
+ 25
+ 0
+ +1.0
+ 51
+ +98
+ 79
+ 5
+ 74
+ 4
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Linear
+ Custom
+
+
+ 1, 2
+ 50, 47
+ 123, 131
+ 192, 189
+ 255, 255
+
+
+
+
+ 0, 26
+ 34, 38
+ 92, 88
+ 163, 177
+ 222, 233
+ 255, 255
+
+
+
+
+ 0, 14
+ 34, 27
+ 90, 88
+ 163, 180
+ 255, 255
+
+
+
+
+ 0, 6
+ 37, 26
+ 90, 88
+ 161, 182
+ 215, 228
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 8.7.1
+ -2
+ 0
+ Custom
+ -14
+ image/jpeg
+ 2019-02-16T06:37:31.93
+ 2019-02-16T06:37:31.93
+ Adobe Photoshop Lightroom 5.7.1 (Macintosh)
+ 2019-02-16T19:26-05:00
+ 2019-02-16T19:26:00-05:00
+ 3
+
+
+ 7AE2C6EAF539AF535316F1B6DBCC874C
+ 7AE2C6EAF539AF535316F1B6DBCC874C
+
+
+ xmp.did:04e69b56-089f-4b0b-a69d-08111a57f914
+
+
+
+
+ derived
+ saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:04e69b56-089f-4b0b-a69d-08111a57f914
+ Adobe Photoshop Lightroom 5.7.1 (Macintosh)
+ 2019-02-16T19:26-05:00
+
+
+
+
+ xmp.iid:04e69b56-089f-4b0b-a69d-08111a57f914
+ 7AE2C6EAF539AF535316F1B6DBCC874C
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_8_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_8_formatted_compact.xmp
new file mode 100644
index 0000000..7370796
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_8_formatted_compact.xmp
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 1, 2
+ 50, 47
+ 123, 131
+ 192, 189
+ 255, 255
+
+
+
+
+ 0, 26
+ 34, 38
+ 92, 88
+ 163, 177
+ 222, 233
+ 255, 255
+
+
+
+
+ 0, 14
+ 34, 27
+ 90, 88
+ 163, 180
+ 255, 255
+
+
+
+
+ 0, 6
+ 37, 26
+ 90, 88
+ 161, 182
+ 215, 228
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_9.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_9.xmp
new file mode 100644
index 0000000..b0356ec
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_9.xmp
@@ -0,0 +1,379 @@
+
+
+
+
+
+ 708/10
+ 47813
+ 200.0-400.0 mm f/4.0
+ 168
+ 2000/10 4000/10 40/10 40/10
+ 5000335
+
+
+
+ image/jpeg
+
+
+
+ 2019-11-03T14:55:39.04
+
+
+
+ 2019-11-03T14:55:39.04
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-11-04T20:35:45-08:00
+ 2019-11-04T20:35:45-08:00
+ 2
+
+
+
+
+ xmp.did:0ce5a9f4-a7f1-47a7-a5be-1486c7c1fa92
+ xmp.iid:0ce5a9f4-a7f1-47a7-a5be-1486c7c1fa92
+ 4FE322F15156DA8278397D8637530F9C
+
+ xmp.did:e99f0b53-6af5-4350-ba23-7639d7cddd25
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:0ce5a9f4-a7f1-47a7-a5be-1486c7c1fa92
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-11-04T20:35:45-08:00
+
+
+ converted
+ from image/jpeg to image/x-nikon-nef
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+ saved
+ /
+ xmp.iid:e99f0b53-6af5-4350-ba23-7639d7cddd25
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-11-04T20:35:45-08:00
+
+
+
+ xmp.iid:e99f0b53-6af5-4350-ba23-7639d7cddd25
+ 4FE322F15156DA8278397D8637530F9C
+
+
+
+ True
+ 0
+ 0
+ 0
+ 0
+ Adobe Standard
+ 7453B3B4E044C00997A55C30D2002AEF
+
+
+
+ true
+ 1.000000
+
+
+
+ 0.000000
+ 0.514759
+ +50
+ true
+ 0.447081
+ 1.000000
+ +50
+ 0.473742
+ 0
+ 0.494124
+ +2
+ Mask/CircularGradient
+
+
+
+
+ 0.500000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0
+ +2
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.097475
+ 0.000000
+ -0.118550
+ 0.000000
+ 0.107831
+ 0.000000
+ 0.186773
+ -0.194646
+ 0.000000
+ 0.000000
+ 0.095294
+ 0.006995
+ 0.120094
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.017533
+ Correction
+
+
+
+ +27
+ 25
+ 50
+ 50
+ -43
+ False
+ -0.948469
+ 0.803565
+ 0
+ 0.309558
+ 0.702666
+ 0.425459
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +24
+ +0.60
+ 0
+ 0
+ 0
+ True
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.2
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+ 0
+ -79
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ 0
+ 0
+ +12
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +67
+ 25
+ 0
+ +1.0
+ 40
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5700
+ +22
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Medium Contrast
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2
+ +9
+ 0
+ Custom
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_90.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_90.xmp
new file mode 100644
index 0000000..b7f5ee5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_90.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_90_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_90_formatted_canonical.xmp
new file mode 100644
index 0000000..019e2a3
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_90_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.458Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.458Z
+ 2023-07-03T10:24:16.000
+ 7A0EECB7-08FD-42A8-AC98-8EC8999F3A8E
+ xmp.iid:28D73372-DEC7-4478-A212-55C86F066A72
+ 7A0EECB7-08FD-42A8-AC98-8EC8999F3A8E
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_90_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_90_formatted_compact.xmp
new file mode 100644
index 0000000..49d8ac5
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_90_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_91.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_91.xmp
new file mode 100644
index 0000000..72fc339
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_91.xmp
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_91_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_91_formatted_canonical.xmp
new file mode 100644
index 0000000..ab57a6e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_91_formatted_canonical.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.505Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.505Z
+ 2023-07-03T10:24:16.000
+ 06799B32-739F-4B7A-9C18-DDA3EC2E3B3E
+ xmp.iid:7180B2FD-3396-45B4-8B8A-EB22F76B883A
+ 06799B32-739F-4B7A-9C18-DDA3EC2E3B3E
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_91_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_91_formatted_compact.xmp
new file mode 100644
index 0000000..36cb46a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_91_formatted_compact.xmp
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_92.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_92.xmp
new file mode 100644
index 0000000..113c448
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_92.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_92_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_92_formatted_canonical.xmp
new file mode 100644
index 0000000..15214af
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_92_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.490Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.490Z
+ 2023-07-03T10:24:16.000
+ 389323A2-96D9-402B-9007-EC3F947F0876
+ xmp.iid:67E86FCF-293C-4DA8-97C5-61F5F747326E
+ 389323A2-96D9-402B-9007-EC3F947F0876
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_92_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_92_formatted_compact.xmp
new file mode 100644
index 0000000..af44ac0
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_92_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_93.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_93.xmp
new file mode 100644
index 0000000..0258d61
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_93.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_93_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_93_formatted_canonical.xmp
new file mode 100644
index 0000000..4aaabbe
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_93_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.552Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.552Z
+ 2023-07-03T10:24:16.000
+ 6C570B9F-04D8-4ED2-BE8F-BC3FD95D466F
+ xmp.iid:86247912-8A22-438B-A8DE-3AE990B31B85
+ 6C570B9F-04D8-4ED2-BE8F-BC3FD95D466F
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_93_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_93_formatted_compact.xmp
new file mode 100644
index 0000000..2235b93
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_93_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_94.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_94.xmp
new file mode 100644
index 0000000..11ccf8a
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_94.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_94_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_94_formatted_canonical.xmp
new file mode 100644
index 0000000..5ab0230
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_94_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.568Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.568Z
+ 2023-07-03T10:24:16.000
+ EE759768-D896-456E-94FC-17C9FEC1A54E
+ xmp.iid:02569840-6D24-4C8C-868C-684C2B766BF6
+ EE759768-D896-456E-94FC-17C9FEC1A54E
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_94_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_94_formatted_compact.xmp
new file mode 100644
index 0000000..e180bd6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_94_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_95.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_95.xmp
new file mode 100644
index 0000000..cd51bed
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_95.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_95_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_95_formatted_canonical.xmp
new file mode 100644
index 0000000..3803254
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_95_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:17.000
+
+ 2023-07-03T10:24:17.000
+ 2023-07-03T08:38:51.599Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:17.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:17.000
+ Green
+ 2023-07-03T08:38:51.599Z
+ 2023-07-03T10:24:17.000
+ 0BB45933-3304-4C9E-B359-D072C9141B52
+ xmp.iid:6D78D38D-ADBC-42C7-A07B-4F104F872382
+ 0BB45933-3304-4C9E-B359-D072C9141B52
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_95_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_95_formatted_compact.xmp
new file mode 100644
index 0000000..3c3f7bc
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_95_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_96.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_96.xmp
new file mode 100644
index 0000000..927852d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_96.xmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_96_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_96_formatted_canonical.xmp
new file mode 100644
index 0000000..3966720
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_96_formatted_canonical.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.631Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.631Z
+ 2023-07-03T10:24:16.000
+ 510B4602-F43A-4170-86A8-00241CDDD572
+ xmp.iid:11D44101-BE0C-4307-A3AC-9B563423BC5A
+ 510B4602-F43A-4170-86A8-00241CDDD572
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_96_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_96_formatted_compact.xmp
new file mode 100644
index 0000000..a5c6ce6
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_96_formatted_compact.xmp
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_97.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_97.xmp
new file mode 100644
index 0000000..e27c0cc
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_97.xmp
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Äußerst
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_97_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_97_formatted_canonical.xmp
new file mode 100644
index 0000000..8da2b75
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_97_formatted_canonical.xmp
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+ Äußerst
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.647Z
+ false
+ false
+ 1
+
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+
+
+
+
+ 4200
+ pixel
+ 3173
+
+
+
+
+
+
+
+
+ 0.057278
+ normalized
+ 0.070046
+ 0.536995
+ 0.476963
+
+
+ mylio_face
+ Äußerst
+ Face
+
+
+
+
+
+
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.647Z
+ 2023-07-03T10:24:16.000
+ 8A74B179-ACDF-4025-AA40-145F29BB674F
+ xmp.iid:346C5792-F6A4-4AE5-B996-1290E4708765
+ 8A74B179-ACDF-4025-AA40-145F29BB674F
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_97_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_97_formatted_compact.xmp
new file mode 100644
index 0000000..112eaf2
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_97_formatted_compact.xmp
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+ Äußerst
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_98.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_98.xmp
new file mode 100644
index 0000000..39c3cb1
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_98.xmp
@@ -0,0 +1,70 @@
+
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cool Guy
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_98_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_98_formatted_canonical.xmp
new file mode 100644
index 0000000..7d91f8d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_98_formatted_canonical.xmp
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+ Cool Guy
+
+
+ 2023-07-03T10:24:15.000
+
+ 2023-07-03T10:24:15.000
+ 2023-07-03T08:38:51.678Z
+ false
+ false
+ 1
+
+
+ test
+
+
+ 2023-07-03T10:24:15.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+
+
+
+
+ 6400
+ pixel
+ 5120
+
+
+
+
+
+
+
+
+ 0.788569
+ normalized
+ 0.742454
+ 0.478764
+ 0.472614
+
+
+ mylio_face
+ Cool Guy
+ Face
+
+
+
+
+
+
+ 2023-07-03T10:24:15.000
+ Green
+ 2023-07-03T08:38:51.678Z
+ 2023-07-03T10:24:15.000
+ 1
+ 8464164F-5DC3-45BD-B6B7-64AD12447273
+ xmp.iid:9EC3DA31-39F9-4138-AD0C-04ABB0F284DB
+ 8464164F-5DC3-45BD-B6B7-64AD12447273
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_98_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_98_formatted_compact.xmp
new file mode 100644
index 0000000..88164f0
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_98_formatted_compact.xmp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+ Cool Guy
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_99.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_99.xmp
new file mode 100644
index 0000000..530d275
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_99.xmp
@@ -0,0 +1,38 @@
+
+
+
+
+
+ test
+
+
+
+
+
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_99_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_99_formatted_canonical.xmp
new file mode 100644
index 0000000..ce9049e
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_99_formatted_canonical.xmp
@@ -0,0 +1,40 @@
+
+
+
+
+ 2023-07-03T10:24:16.000
+
+ 2023-07-03T10:24:16.000
+ 2023-07-03T08:38:51.709Z
+ false
+ false
+ 1
+
+
+ test
+
+
+ 2023-07-03T10:24:16.000
+ 53,13.1662N
+ 8,14.3757E
+ 2.2.0.0
+ 0
+ 0
+ 0
+ 2023-07-03T10:24:16.000
+ Green
+ 2023-07-03T08:38:51.709Z
+ 2023-07-03T10:24:16.000
+ 1
+ 68523A28-E7F6-484D-B15C-ED4A94251289
+ xmp.iid:2908FDC8-A8E0-4D75-9C4B-CF6CC6E1D57D
+ 68523A28-E7F6-484D-B15C-ED4A94251289
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_99_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_99_formatted_compact.xmp
new file mode 100644
index 0000000..a039912
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_99_formatted_compact.xmp
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ test
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_9_formatted_canonical.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_9_formatted_canonical.xmp
new file mode 100644
index 0000000..634286d
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_9_formatted_canonical.xmp
@@ -0,0 +1,361 @@
+
+
+
+
+ 708/10
+ 47813
+ 200.0-400.0 mm f/4.0
+ 168
+ 2000/10 4000/10 40/10 40/10
+ 5000335
+ True
+ 0
+ 0
+ 0
+ 0
+ Adobe Standard
+ 7453B3B4E044C00997A55C30D2002AEF
+
+
+
+
+ true
+ 1.000000
+
+
+
+
+ 0.000000
+ 0.514759
+ +50
+ true
+ 0.447081
+ 1.000000
+ +50
+ 0.473742
+ 0
+ 0.494124
+ +2
+ Mask/CircularGradient
+
+
+
+
+
+
+ 0.500000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0.500000
+ 1.000000
+ 0.000000
+ 0
+ +2
+
+
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.097475
+ 0.000000
+ -0.118550
+ 0.000000
+ 0.107831
+ 0.000000
+ 0.186773
+ -0.194646
+ 0.000000
+ 0.000000
+ 0.095294
+ 0.006995
+ 0.120094
+ 0.000000
+ 0.000000
+ 240.000000
+ 0.000000
+ 0.017533
+ Correction
+
+
+
+
+ +27
+ 25
+ 50
+ 50
+ -43
+ False
+ -0.948469
+ 0.803565
+ 0
+ 0.309558
+ 0.702666
+ 0.425459
+ 0
+ 60
+ 40
+ 0
+ 70
+ 30
+ +24
+ +0.60
+ 0
+ 0
+ 0
+ True
+ True
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ LensDefaults
+
+
+ 1.000000
+
+
+ Profiles
+
+
+ Adobe Color
+
+
+ Adobe Standard
+ False
+ E1095149FDB39D7A057BAB208837E2E1
+ 11.0
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 11.2
+
+
+ false
+ false
+ false
+ B952C231111CD8E0ECCF14B86BAA7077
+
+
+ 0
+ -79
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ False
+ 0
+ 75
+ 0
+ 0
+ 50
+ 25
+ 0
+ 0
+ 0
+ 0.0
+ 100
+ 0
+ 0
+ 0.00
+ 0.00
+ 0
+ 11.0
+ 0
+ 0
+ +12
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ +67
+ 25
+ 0
+ +1.0
+ 40
+ 0
+ 0
+ 0
+ 0
+ 0
+ 5700
+ +22
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ Medium Contrast
+ Linear
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+ 0
+ 0
+ 0.5
+ 0.5
+ 35
+ 0
+ 0
+ False
+ 6
+ 151388160
+ 11.2
+ +9
+ 0
+ Custom
+ 0
+ image/jpeg
+ 2019-11-03T14:55:39.04
+ 2019-11-03T14:55:39.04
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-11-04T20:35:45-08:00
+ 2019-11-04T20:35:45-08:00
+ 2
+
+
+ xmp.did:0ce5a9f4-a7f1-47a7-a5be-1486c7c1fa92
+ xmp.iid:0ce5a9f4-a7f1-47a7-a5be-1486c7c1fa92
+ 4FE322F15156DA8278397D8637530F9C
+
+
+ xmp.did:e99f0b53-6af5-4350-ba23-7639d7cddd25
+
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:0ce5a9f4-a7f1-47a7-a5be-1486c7c1fa92
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-11-04T20:35:45-08:00
+
+
+
+
+ converted
+ from image/jpeg to image/x-nikon-nef
+
+
+
+
+ derived
+ converted from image/x-nikon-nef to image/jpeg, saved to new location
+
+
+
+
+ saved
+ /
+ xmp.iid:e99f0b53-6af5-4350-ba23-7639d7cddd25
+ Adobe Photoshop Lightroom Classic 8.2 (Macintosh)
+ 2019-11-04T20:35:45-08:00
+
+
+
+
+ xmp.iid:e99f0b53-6af5-4350-ba23-7639d7cddd25
+ 4FE322F15156DA8278397D8637530F9C
+
+
+
+
\ No newline at end of file
diff --git a/src/commonTest/resources/com/ashampoo/xmp/sample_9_formatted_compact.xmp b/src/commonTest/resources/com/ashampoo/xmp/sample_9_formatted_compact.xmp
new file mode 100644
index 0000000..6cd4104
--- /dev/null
+++ b/src/commonTest/resources/com/ashampoo/xmp/sample_9_formatted_compact.xmp
@@ -0,0 +1,337 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Profiles
+
+
+
+
+
+
+ 0, 0
+ 22, 16
+ 40, 35
+ 127, 127
+ 224, 230
+ 240, 246
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+ 0, 0
+ 32, 22
+ 64, 56
+ 128, 128
+ 192, 196
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+ 0, 0
+ 255, 255
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/posixMain/kotlin/PosixByteArrayReader.kt b/src/posixMain/kotlin/PosixByteArrayReader.kt
new file mode 100644
index 0000000..d84cbcc
--- /dev/null
+++ b/src/posixMain/kotlin/PosixByteArrayReader.kt
@@ -0,0 +1,48 @@
+import kotlinx.cinterop.CPointer
+import kotlinx.cinterop.UnsafeNumber
+import kotlinx.cinterop.memScoped
+import kotlinx.cinterop.refTo
+import platform.posix.FILE
+import platform.posix.SEEK_END
+import platform.posix.fclose
+import platform.posix.fopen
+import platform.posix.fread
+import platform.posix.fseek
+import platform.posix.ftell
+import platform.posix.perror
+import platform.posix.rewind
+
+@OptIn(UnsafeNumber::class)
+internal fun readFileAsByteArray(filePath: String): ByteArray? = memScoped {
+
+ /* Note: Mode "rb" is for reading binary files. */
+ val file: CPointer? = fopen(filePath, "rb")
+
+ if (file == null) {
+ perror("Failed to open file: $filePath")
+ return null
+ }
+
+ /* Move the cursor to the end of the file. */
+ fseek(file, 0, SEEK_END)
+ val fileSize = ftell(file)
+ rewind(file)
+
+ val buffer = ByteArray(fileSize.toInt())
+
+ val bytesReadCount: ULong = fread(
+ buffer.refTo(0),
+ 1.toULong(), // Number of items
+ fileSize.toULong(), // Size to read
+ file
+ )
+
+ fclose(file)
+
+ if (bytesReadCount != fileSize.toULong()) {
+ perror("Did not read file completely: $bytesReadCount != $fileSize")
+ return null
+ }
+
+ return buffer
+}
diff --git a/src/posixMain/kotlin/main.kt b/src/posixMain/kotlin/main.kt
new file mode 100644
index 0000000..951b15f
--- /dev/null
+++ b/src/posixMain/kotlin/main.kt
@@ -0,0 +1,36 @@
+import com.ashampoo.xmp.XMPMetaFactory
+import com.ashampoo.xmp.options.SerializeOptions
+import platform.posix.perror
+
+private val xmpSerializeOptionsCompact =
+ SerializeOptions()
+ .setOmitXmpMetaElement(false)
+ .setOmitPacketWrapper(false)
+ .setUseCompactFormat(true)
+ .setUseCanonicalFormat(false)
+ .setSort(true)
+
+fun main(args: Array) {
+
+ if (args.size != 1) {
+ println("USAGE: Must be called with one argument.")
+ return
+ }
+
+ val filePath = args.first()
+
+ val bytes = readFileAsByteArray(filePath)
+
+ if (bytes == null) {
+ perror("File could not be read: $filePath")
+ return
+ }
+
+ val xmp = bytes.decodeToString()
+
+ val xmpMeta = XMPMetaFactory.parseFromString(xmp)
+
+ val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptionsCompact)
+
+ println(newXmp)
+}