From 98d66c48ed8b899fb1590b6f7d5bba4dcc48ce71 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Mon, 6 Oct 2025 16:39:22 +0200 Subject: [PATCH] [jOOQ/jOOQ#19147] Result::formatXML should offer a way to configure NULL encoding --- jOOQ/src/main/java/org/jooq/XMLFormat.java | 90 ++++++++++++++++--- .../java/org/jooq/impl/AbstractResult.java | 55 +++++++----- 2 files changed, 110 insertions(+), 35 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/XMLFormat.java b/jOOQ/src/main/java/org/jooq/XMLFormat.java index 8976c12422..d35fbf59d9 100644 --- a/jOOQ/src/main/java/org/jooq/XMLFormat.java +++ b/jOOQ/src/main/java/org/jooq/XMLFormat.java @@ -64,6 +64,7 @@ public final class XMLFormat { boolean header; RecordFormat recordFormat; boolean quoteNested; + NullFormat nullFormat; public XMLFormat() { this( @@ -76,7 +77,8 @@ public final class XMLFormat { null, true, RecordFormat.VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE, - false + false, + NullFormat.EMPTY_ELEMENT ); } @@ -90,7 +92,8 @@ public final class XMLFormat { String[] indented, boolean header, RecordFormat recordFormat, - boolean quoteNested + boolean quoteNested, + NullFormat nullFormat ) { this.mutable = mutable; this.xmlns = xmlns; @@ -107,6 +110,7 @@ public final class XMLFormat { this.header = header; this.recordFormat = recordFormat; this.quoteNested = quoteNested; + this.nullFormat = nullFormat; } /** @@ -132,7 +136,8 @@ public final class XMLFormat { indented, header, recordFormat, - quoteNested + quoteNested, + nullFormat ); else return this; @@ -158,7 +163,8 @@ public final class XMLFormat { indented, header, recordFormat, - quoteNested + quoteNested, + nullFormat ); } @@ -189,7 +195,8 @@ public final class XMLFormat { null, header, recordFormat, - quoteNested + quoteNested, + nullFormat ); } @@ -220,7 +227,8 @@ public final class XMLFormat { indented, header, recordFormat, - quoteNested + quoteNested, + nullFormat ); } @@ -252,7 +260,8 @@ public final class XMLFormat { null, header, recordFormat, - quoteNested + quoteNested, + nullFormat ); } @@ -283,7 +292,8 @@ public final class XMLFormat { null, header, recordFormat, - quoteNested + quoteNested, + nullFormat ); } @@ -335,7 +345,8 @@ public final class XMLFormat { indented, newHeader, recordFormat, - quoteNested + quoteNested, + nullFormat ); } @@ -367,7 +378,8 @@ public final class XMLFormat { indented, header, newRecordFormat, - quoteNested + quoteNested, + nullFormat ); } @@ -401,7 +413,8 @@ public final class XMLFormat { indented, header, recordFormat, - newQuoteNested + newQuoteNested, + nullFormat ); } @@ -413,6 +426,40 @@ public final class XMLFormat { return quoteNested; } + /** + * Whether nested {@link XML} content should be quoted like a string, or + * nested into XML formatted output. + */ + @NotNull + public final XMLFormat nullFormat(NullFormat newNullFormat) { + if (mutable) { + nullFormat = newNullFormat; + return this; + } + else + return new XMLFormat( + mutable, + xmlns, + format, + newline, + globalIndent, + indent, + indented, + header, + recordFormat, + quoteNested, + newNullFormat + ); + } + + /** + * Whether nested {@link XML} content should be quoted like a string, or + * nested into XML formatted output. + */ + public final NullFormat nullFormat() { + return nullFormat; + } + /** * The format of individual XML records. */ @@ -433,4 +480,25 @@ public final class XMLFormat { */ COLUMN_NAME_ELEMENTS, } + + /** + * The format of a null value. + */ + public enum NullFormat { + + /** + * A null value is represented by an empty tag. + */ + EMPTY_ELEMENT, + + /** + * A null value is represented by an absent tag. + */ + ABSENT_ELEMENT, + + /** + * A null value is represented by a xsi:nil="true" attribute. + */ + XSI_NIL + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java b/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java index d0dc313977..357eea757e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java @@ -41,6 +41,8 @@ import static java.lang.Math.max; import static java.lang.Math.min; import static java.util.stream.Collectors.joining; import static org.jooq.JSONFormat.NullFormat.ABSENT_ON_NULL; +import static org.jooq.XMLFormat.NullFormat.ABSENT_ELEMENT; +import static org.jooq.XMLFormat.NullFormat.XSI_NIL; import static org.jooq.XMLFormat.RecordFormat.COLUMN_NAME_ELEMENTS; import static org.jooq.XMLFormat.RecordFormat.VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE; import static org.jooq.conf.SettingsTools.renderLocale; @@ -892,32 +894,37 @@ abstract class AbstractResult extends AbstractFormattable impl ? escapeXML(fields.field(index).getName()) : "value"; - writer.append("<" + tag); - if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) { - writer.append(" field=\""); - writer.append(escapeXML(fields.field(index).getName())); - writer.append("\""); - } - - if (value == null) { - writer.append("/>"); - } - else { - writer.append(">"); - - if (value instanceof Formattable f) { - writer.append(newline).append(format.indentString(recordLevel + 2)); - int previous = format.globalIndent(); - f.formatXML(writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 2))); - format.globalIndent(previous); - writer.append(newline).append(format.indentString(recordLevel + 1)); + if (value != null || format.nullFormat() != ABSENT_ELEMENT) { + writer.append("<" + tag); + if (format.recordFormat() == VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE) { + writer.append(" field=\""); + writer.append(escapeXML(fields.field(index).getName())); + writer.append("\""); } - else if (value instanceof XML && !format.quoteNested()) - writer.append(((XML) value).data()); - else - writer.append(escapeXML(format0(value, false, false))); - writer.append(""); + if (value == null) { + if (format.nullFormat() == XSI_NIL) + writer.append(" xsi:nil=\"true\""); + + writer.append("/>"); + } + else { + writer.append(">"); + + if (value instanceof Formattable f) { + writer.append(newline).append(format.indentString(recordLevel + 2)); + int previous = format.globalIndent(); + f.formatXML(writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 2))); + format.globalIndent(previous); + writer.append(newline).append(format.indentString(recordLevel + 1)); + } + else if (value instanceof XML && !format.quoteNested()) + writer.append(((XML) value).data()); + else + writer.append(escapeXML(format0(value, false, false))); + + writer.append(""); + } } }