diff --git a/README.md b/README.md
index 0455b95..2755017 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,6 @@
[](https://github.com/felipestanzani/jtoon/actions/workflows/build.yml)
[](https://github.com/felipestanzani/jtoon/actions/workflows/release.yml)
[](https://central.sonatype.com/artifact/com.felipestanzani/jtoon)
-
**Token-Oriented Object Notation** is a compact, human-readable format designed for passing structured data to Large Language Models with significantly reduced token usage.
@@ -67,7 +66,7 @@ JToon is available on Maven Central. Add it to your project using your preferred
```gradle
dependencies {
- implementation 'com.felipestanzani:jtoon:0.1.3'
+ implementation 'com.felipestanzani:jtoon:0.1.2'
}
```
@@ -75,7 +74,7 @@ dependencies {
```kotlin
dependencies {
- implementation("com.felipestanzani:jtoon:0.1.3")
+ implementation("com.felipestanzani:jtoon:0.1.2")
}
```
@@ -85,7 +84,7 @@ dependencies {
com.felipestanzani
jtoon
- 0.1.3
+ 0.1.2
```
@@ -196,6 +195,10 @@ Number normalization examples:
### `JToon.encodeJson(String json, EncodeOptions options): String`
+### `JToon.encodeXml(String xml): String`
+
+### `JToon.encodeXml(String xml, EncodeOptions options): String`
+
Converts any Java object or JSON-string to TOON format.
**Parameters:**
@@ -210,6 +213,10 @@ For `encodeJson` overloads:
- `json` – A valid JSON string to be parsed and encoded. Invalid or blank JSON throws `IllegalArgumentException`.
+For `encodeXml` overloads:
+
+- `xml` – A valid XML string to be parsed and encoded. Invalid or blank XML throws `IllegalArgumentException`.
+
**Returns:**
A TOON-formatted string with no trailing newline or spaces.
@@ -262,6 +269,58 @@ user:
tags[2]: reading,gaming
```
+#### Encode XML
+
+```java
+String xml = "John25";
+System.out.println(JToon.encodeXml(xml));
+```
+
+Output:
+
+```
+user:
+ name: John
+ age: 25
+```
+
+#### XML to TOON Conversion Use Cases
+
+XML to TOON conversion is particularly useful in scenarios where:
+
+- Legacy System Integration**: Converting XML APIs or data feeds from older systems to TOON for efficient LLM processing
+- Configuration Files**: Transforming XML configuration files to TOON format for AI-assisted configuration analysis
+- Data Exchange**: Converting XML data exchange formats to TOON for reduced token usage in AI conversations
+- Log Analysis**: Processing XML formatted logs and converting them to TOON for AI-powered log analysis
+- Web Services**: Converting SOAP XML responses or REST XML payloads to TOON for AI interpretation
+
+For example, converting a complex XML document:
+```xml
+
+ TechCorp
+
+
+ Engineering
+ 50
+
+
+ Marketing
+ 20
+
+
+
+```
+
+To TOON:
+```
+company:
+ name: TechCorp
+ departments[2]{name,employees}:
+ Engineering,50
+ Marketing,20
+```
+This conversion provides significant token savings while maintaining the hierarchical structure of the original XML.
+
#### Delimiter Options
The `delimiter` option allows you to choose between comma (default), tab, or pipe delimiters for array values and tabular rows. Alternative delimiters can provide additional token savings in specific contexts.
diff --git a/build.gradle b/build.gradle
index a3980c1..f91ce63 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,6 +37,7 @@ jacoco {
dependencies {
implementation 'tools.jackson.core:jackson-databind:3.0.2'
implementation 'tools.jackson.module:jackson-module-afterburner:3.0.2'
+ implementation 'tools.jackson.dataformat:jackson-dataformat-xml:3.0.2'
testImplementation platform('org.junit:junit-bom:6.0.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
@@ -75,4 +76,4 @@ jacocoTestCoverageVerification {
}
}
}
-check.dependsOn jacocoTestReport
\ No newline at end of file
+check.dependsOn jacocoTestReport
diff --git a/src/main/java/com/felipestanzani/jtoon/JToon.java b/src/main/java/com/felipestanzani/jtoon/JToon.java
index ff44767..379d180 100644
--- a/src/main/java/com/felipestanzani/jtoon/JToon.java
+++ b/src/main/java/com/felipestanzani/jtoon/JToon.java
@@ -3,8 +3,40 @@
import com.felipestanzani.jtoon.decoder.ValueDecoder;
import com.felipestanzani.jtoon.encoder.ValueEncoder;
import com.felipestanzani.jtoon.normalizer.JsonNormalizer;
+import com.felipestanzani.jtoon.normalizer.XmlNormalizer;
import tools.jackson.databind.JsonNode;
+/**
+ * Main API for encoding and decoding JToon format.
+ *
+ *
+ * JToon is a structured text format that represents JSON-like data in a more
+ * human-readable way, with support for tabular arrays and inline formatting.
+ *
+ *
+ * Usage Examples:
+ *
+ * {@code
+ * // Encode a Java object with default options
+ * String toon = JToon.encode(myObject);
+ *
+ * // Encode with custom options
+ * EncodeOptions options = new EncodeOptions(4, Delimiter.PIPE, true);
+ * String toon = JToon.encode(myObject, options);
+ *
+ * // Encode a plain JSON string directly
+ * String toon = JToon.encodeJson("{\"id\":123,\"name\":\"Ada\"}");
+ *
+ * // Encode a plain XML string directly
+ * String toon = JToon.encodeXml("123Ada");
+ *
+ * // Decode TOON back to Java objects
+ * Object result = JToon.decode(toon);
+ *
+ * // Decode TOON directly to JSON string
+ * String json = JToon.decodeToJson(toon);
+ * }
+ */
public final class JToon {
private JToon() {
@@ -79,6 +111,42 @@ public static String encodeJson(String json, EncodeOptions options) {
return ValueEncoder.encodeValue(parsed, options);
}
+ /**
+ * Encodes a plain XML string to TOON format using default options.
+ *
+ *
+ * This is a convenience overload that parses the XML string and encodes it
+ * without requiring callers to create a {@code JsonNode} or intermediate
+ * objects.
+ *
+ *
+ * @param xml The XML string to encode (must be valid XML)
+ * @return The TOON-formatted string
+ * @throws IllegalArgumentException if the input is not valid XML
+ */
+ public static String encodeXml(String xml) {
+ return encodeXml(xml, EncodeOptions.DEFAULT);
+ }
+
+ /**
+ * Encodes a plain XML string to TOON format using custom options.
+ *
+ *
+ * Parsing is delegated to
+ * {@link com.felipestanzani.jtoon.normalizer.XmlNormalizer#parse(String)}
+ * to maintain separation of concerns.
+ *
+ *
+ * @param xml The XML string to encode (must be valid XML)
+ * @param options Encoding options (indent, delimiter, length marker)
+ * @return The TOON-formatted string
+ * @throws IllegalArgumentException if the input is not valid XML
+ */
+ public static String encodeXml(String xml, EncodeOptions options) {
+ JsonNode parsed = XmlNormalizer.parse(xml);
+ return ValueEncoder.encodeValue(parsed, options);
+ }
+
/**
* Decodes a TOON-formatted string to Java objects using default options.
*
diff --git a/src/main/java/com/felipestanzani/jtoon/normalizer/XmlNormalizer.java b/src/main/java/com/felipestanzani/jtoon/normalizer/XmlNormalizer.java
new file mode 100644
index 0000000..d0d3667
--- /dev/null
+++ b/src/main/java/com/felipestanzani/jtoon/normalizer/XmlNormalizer.java
@@ -0,0 +1,40 @@
+package com.felipestanzani.jtoon.normalizer;
+
+import tools.jackson.databind.JsonNode;
+import tools.jackson.dataformat.xml.XmlMapper;
+
+/**
+ * Normalizes XML strings to Jackson JsonNode representation.
+ * Converts XML structure to JSON-compatible format for TOON encoding.
+ */
+public final class XmlNormalizer {
+
+ private static final XmlMapper XML_MAPPER = XmlMapper.builder().build();
+
+ private XmlNormalizer() {
+ throw new UnsupportedOperationException("Utility class cannot be instantiated");
+ }
+
+ /**
+ * Parses an XML string into a JsonNode using the shared XmlMapper.
+ *
+ * This centralizes XML parsing concerns to keep the public API thin and
+ * maintain separation of responsibilities between parsing, normalization,
+ * and encoding.
+ *
+ *
+ * @param xml The XML string to parse (must be valid XML)
+ * @return Parsed JsonNode
+ * @throws IllegalArgumentException if the input is blank or not valid XML
+ */
+ public static JsonNode parse(String xml) {
+ if (xml == null || xml.trim().isEmpty()) {
+ throw new IllegalArgumentException("Invalid XML");
+ }
+ try {
+ return XML_MAPPER.readTree(xml);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid XML", e);
+ }
+ }
+}
diff --git a/src/test/java/com/felipestanzani/jtoon/JToonTest.java b/src/test/java/com/felipestanzani/jtoon/JToonTest.java
index d59a874..f585176 100644
--- a/src/test/java/com/felipestanzani/jtoon/JToonTest.java
+++ b/src/test/java/com/felipestanzani/jtoon/JToonTest.java
@@ -747,6 +747,115 @@ void noTrailingNewline() {
}
}
+ @Nested
+ @DisplayName("XML tests")
+ class XmlTests {
+
+ @Test
+ @DisplayName("encodes XML with custom options")
+ void encodesXmlWithOptions() {
+ String xml = "123Ada";
+ EncodeOptions options = new EncodeOptions(4, Delimiter.PIPE, true);
+ String result = JToon.encodeXml(xml, options);
+ assertEquals("id: \"123\"\nname: Ada", result);
+ }
+
+ @Test
+ @DisplayName("throws exception for invalid XML")
+ void throwsForInvalidXml() {
+ String invalidXml = "123Ada";
+ assertThrows(IllegalArgumentException.class, () -> JToon.encodeXml(invalidXml));
+ }
+
+ @Test
+ @DisplayName("throws exception for null XML")
+ void throwsForNullXml() {
+ assertThrows(IllegalArgumentException.class, () -> JToon.encodeXml(null));
+ }
+
+ @Test
+ @DisplayName("throws exception for empty XML")
+ void throwsForEmptyXml() {
+ assertThrows(IllegalArgumentException.class, () -> JToon.encodeXml(""));
+ }
+
+ @Nested
+ @DisplayName("XML structures (positive test cases)")
+ class XmlStructuresPositive {
+
+ @Test
+ @DisplayName("encodes XML successfully")
+ void encodesXmlSuccessfully() {
+ String xml = "John25";
+ String result = JToon.encodeXml(xml);
+ assertEquals("name: John\nage: \"25\"", result);
+ }
+
+ @Test
+ @DisplayName("encodes complex XML successfully")
+ void encodesComplexXmlSuccessfully() {
+ String xml = "TechCorpAlice";
+ String result = JToon.encodeXml(xml);
+ assertEquals("name: TechCorp\nemployees:\n employee:\n name: Alice", result);
+ }
+
+ @Test
+ @DisplayName("encodes XML with attributes")
+ void encodesXmlWithAttributes() {
+ String xml = "Johnjohn@example.com";
+ String result = JToon.encodeXml(xml);
+ assertEquals("id: \"123\"\nactive: \"true\"\nname: John\nemail: john@example.com", result);
+ }
+
+ @Test
+ @DisplayName("encodes deeply nested XML with arrays")
+ void encodesDeeplyNestedXmlWithArrays() {
+ String xml = "TechCorpEngineeringAliceDeveloperBobManagerMarketingCarolDirector";
+ String result = JToon.encodeXml(xml);
+ assertEquals("name: TechCorp\ndepartments:\n department[2]:\n - name: Engineering\n employees:\n employee[2]{name,role}:\n Alice,Developer\n Bob,Manager\n - name: Marketing\n employees:\n employee:\n name: Carol\n role: Director", result);
+ }
+
+ @Test
+ @DisplayName("encodes XML with mixed content and attributes")
+ void encodesXmlWithMixedContentAndAttributes() {
+ String xml = "The Great NovelJane DoeWelcome to the storyThe plot thickens";
+ String result = JToon.encodeXml(xml);
+ String expected = "isbn: 978-3-16-148410-0\ncategory: fiction\ntitle: The Great Novel\nauthor:\n status: bestselling\n \"\": Jane Doe\nchapters:\n chapter[2]{number,title,\"\"}:\n \"1\",Introduction,Welcome to the story\n \"2\",Development,The plot thickens";
+ assertEquals(expected, result);
+ }
+ }
+
+ @Nested
+ @DisplayName("XML error handling (negative test cases)")
+ class XmlErrorHandling {
+
+ @Test
+ @DisplayName("throws exception for invalid XML")
+ void throwsForInvalidXml() {
+ String invalidXml = "123Ada";
+ assertThrows(IllegalArgumentException.class, () -> JToon.encodeXml(invalidXml));
+ }
+
+ @Test
+ @DisplayName("throws exception for null XML input")
+ void throwsForNullXml() {
+ assertThrows(IllegalArgumentException.class, () -> JToon.encodeXml(null));
+ }
+
+ @Test
+ @DisplayName("throws exception for empty XML string")
+ void throwsForEmptyXml() {
+ assertThrows(IllegalArgumentException.class, () -> JToon.encodeXml(""));
+ }
+
+ @Test
+ @DisplayName("throws exception for whitespace-only XML")
+ void throwsForWhitespaceOnlyXml() {
+ assertThrows(IllegalArgumentException.class, () -> JToon.encodeXml(" "));
+ }
+ }
+ }
+
@Nested
@DisplayName("non-JSON-serializable values")
class NonJson {