Skip to content

Commit 371fda9

Browse files
committed
Make empty values configurable
1 parent c11e8bc commit 371fda9

File tree

6 files changed

+91
-29
lines changed

6 files changed

+91
-29
lines changed

src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactory.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public class XmlFactory extends JsonFactory
6666

6767
protected String _cfgNameForTextElement;
6868

69+
protected String _cfgValueForEmptyElement;
70+
6971
protected XmlNameProcessor _nameProcessor;
7072

7173
/*
@@ -107,18 +109,26 @@ public XmlFactory(ObjectCodec oc, XMLInputFactory xmlIn, XMLOutputFactory xmlOut
107109
public XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
108110
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
109111
String nameForTextElem) {
110-
this(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, XmlNameProcessors.newPassthroughProcessor());
112+
this(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE, XmlNameProcessors.newPassthroughProcessor());
113+
}
114+
115+
public XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
116+
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
117+
String nameForTextElem, String valueForEmptyElement) {
118+
this(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, valueForEmptyElement,
119+
XmlNameProcessors.newPassthroughProcessor());
111120
}
112121

113122
protected XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
114123
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
115-
String nameForTextElem, XmlNameProcessor nameProcessor)
124+
String nameForTextElem, String valueForEmptyElement, XmlNameProcessor nameProcessor)
116125
{
117126
super(oc);
118127
_nameProcessor = nameProcessor;
119128
_xmlParserFeatures = xpFeatures;
120129
_xmlGeneratorFeatures = xgFeatures;
121130
_cfgNameForTextElement = nameForTextElem;
131+
_cfgValueForEmptyElement = valueForEmptyElement;
122132
if (xmlIn == null) {
123133
xmlIn = StaxUtil.defaultInputFactory(getClass().getClassLoader());
124134
// as per [dataformat-xml#190], disable external entity expansion by default
@@ -145,6 +155,7 @@ protected XmlFactory(XmlFactory src, ObjectCodec oc)
145155
_xmlParserFeatures = src._xmlParserFeatures;
146156
_xmlGeneratorFeatures = src._xmlGeneratorFeatures;
147157
_cfgNameForTextElement = src._cfgNameForTextElement;
158+
_cfgValueForEmptyElement = src._cfgValueForEmptyElement;
148159
_xmlInputFactory = src._xmlInputFactory;
149160
_xmlOutputFactory = src._xmlOutputFactory;
150161
_nameProcessor = src._nameProcessor;
@@ -161,6 +172,7 @@ protected XmlFactory(XmlFactoryBuilder b)
161172
_xmlParserFeatures = b.formatParserFeaturesMask();
162173
_xmlGeneratorFeatures = b.formatGeneratorFeaturesMask();
163174
_cfgNameForTextElement = b.nameForTextElement();
175+
_cfgValueForEmptyElement = b.valueForEmptyElement();
164176
_xmlInputFactory = b.xmlInputFactory();
165177
_xmlOutputFactory = b.xmlOutputFactory();
166178
_nameProcessor = b.xmlNameProcessor();
@@ -237,7 +249,7 @@ protected Object readResolve() {
237249
throw new IllegalArgumentException(e);
238250
}
239251
return new XmlFactory(_objectCodec, _xmlParserFeatures, _xmlGeneratorFeatures,
240-
inf, outf, _cfgNameForTextElement);
252+
inf, outf, _cfgNameForTextElement, _cfgValueForEmptyElement);
241253
}
242254

243255
/**
@@ -281,6 +293,20 @@ public void setXMLTextElementName(String name) {
281293
public String getXMLTextElementName() {
282294
return _cfgNameForTextElement;
283295
}
296+
297+
/**
298+
* @since 2.17
299+
*/
300+
public void setEmptyElementValue(String value) {
301+
_cfgValueForEmptyElement = value;
302+
}
303+
304+
/**
305+
* @since 2.17
306+
*/
307+
public String getEmptyElementValue() {
308+
return _cfgValueForEmptyElement;
309+
}
284310

285311
/*
286312
/**********************************************************
@@ -560,7 +586,7 @@ public FromXmlParser createParser(XMLStreamReader sr) throws IOException
560586

561587
// false -> not managed
562588
FromXmlParser xp = new FromXmlParser(_createContext(_createContentReference(sr), false),
563-
_parserFeatures, _xmlParserFeatures, _objectCodec, sr, _nameProcessor);
589+
_parserFeatures, _xmlParserFeatures, _objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
564590
if (_cfgNameForTextElement != null) {
565591
xp.setXMLTextElementName(_cfgNameForTextElement);
566592
}
@@ -599,7 +625,7 @@ protected FromXmlParser _createParser(InputStream in, IOContext ctxt) throws IOE
599625
}
600626
sr = _initializeXmlReader(sr);
601627
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
602-
_objectCodec, sr, _nameProcessor);
628+
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
603629
if (_cfgNameForTextElement != null) {
604630
xp.setXMLTextElementName(_cfgNameForTextElement);
605631
}
@@ -617,7 +643,7 @@ protected FromXmlParser _createParser(Reader r, IOContext ctxt) throws IOExcepti
617643
}
618644
sr = _initializeXmlReader(sr);
619645
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
620-
_objectCodec, sr, _nameProcessor);
646+
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
621647
if (_cfgNameForTextElement != null) {
622648
xp.setXMLTextElementName(_cfgNameForTextElement);
623649
}
@@ -644,7 +670,7 @@ protected FromXmlParser _createParser(char[] data, int offset, int len, IOContex
644670
}
645671
sr = _initializeXmlReader(sr);
646672
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
647-
_objectCodec, sr, _nameProcessor);
673+
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
648674
if (_cfgNameForTextElement != null) {
649675
xp.setXMLTextElementName(_cfgNameForTextElement);
650676
}
@@ -678,7 +704,7 @@ protected FromXmlParser _createParser(byte[] data, int offset, int len, IOContex
678704
}
679705
sr = _initializeXmlReader(sr);
680706
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
681-
_objectCodec, sr, _nameProcessor);
707+
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
682708
if (_cfgNameForTextElement != null) {
683709
xp.setXMLTextElementName(_cfgNameForTextElement);
684710
}

src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactoryBuilder.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public class XmlFactoryBuilder extends TSFBuilder<XmlFactory, XmlFactoryBuilder>
5454
*/
5555
protected String _nameForTextElement;
5656

57+
/**
58+
* Set a default value in case of empty an empty element (empty XML tag)
59+
*<p>
60+
* Value used for pseudo-property used for returning empty XML tag.
61+
* Defaults to empty String, but may be changed.
62+
*/
63+
protected String _valueForEmptyElement = FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE;
64+
5765
/**
5866
* Optional {@link ClassLoader} to use for constructing
5967
* {@link XMLInputFactory} and {@kink XMLOutputFactory} instances if
@@ -91,6 +99,7 @@ public XmlFactoryBuilder(XmlFactory base) {
9199
_xmlInputFactory = base._xmlInputFactory;
92100
_xmlOutputFactory = base._xmlOutputFactory;
93101
_nameForTextElement = base._cfgNameForTextElement;
102+
_valueForEmptyElement = base._cfgValueForEmptyElement;
94103
_nameProcessor = base._nameProcessor;
95104
_classLoaderForStax = null;
96105
}
@@ -102,6 +111,8 @@ public XmlFactoryBuilder(XmlFactory base) {
102111

103112
public String nameForTextElement() { return _nameForTextElement; }
104113

114+
public String valueForEmptyElement() { return _valueForEmptyElement; }
115+
105116
public XMLInputFactory xmlInputFactory() {
106117
if (_xmlInputFactory == null) {
107118
return defaultInputFactory();
@@ -213,6 +224,11 @@ public XmlFactoryBuilder nameForTextElement(String name) {
213224
return _this();
214225
}
215226

227+
public XmlFactoryBuilder valueForEmptyElement(String value) {
228+
_valueForEmptyElement = value;
229+
return _this();
230+
}
231+
216232
/**
217233
* @since 2.13 (was misnamed as {@code inputFactory(in) formerly})
218234
*/

src/main/java/com/fasterxml/jackson/dataformat/xml/XmlMapper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public Builder nameForTextElement(String name) {
104104
return this;
105105
}
106106

107+
public Builder valueForEmptyElement(String value) {
108+
_mapper.setValueForEmptyElement(value);
109+
return this;
110+
}
111+
107112
public Builder defaultUseWrapper(boolean state) {
108113
_mapper.setDefaultUseWrapper(state);
109114
return this;
@@ -271,6 +276,10 @@ protected void setXMLTextElementName(String name) {
271276
getFactory().setXMLTextElementName(name);
272277
}
273278

279+
protected void setValueForEmptyElement(String value) {
280+
getFactory().setEmptyElementValue(value);
281+
}
282+
274283
/**
275284
* Since 2.7
276285
*

src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public class FromXmlParser
4040
*/
4141
public final static String DEFAULT_UNNAMED_TEXT_PROPERTY = "";
4242

43+
public final static String DEFAULT_EMPTY_ELEMENT_VALUE = "";
44+
4345
/**
4446
* XML format has some peculiarities, indicated via new (2.12) capability
4547
* system.
@@ -86,19 +88,6 @@ public enum Feature implements FormatFeature
8688
*/
8789
EMPTY_ELEMENT_AS_NULL(false),
8890

89-
/**
90-
* Feature that indicates whether XML Empty elements (ones where there are
91-
* no separate start and end tags, but just one tag that ends with "/&gt;")
92-
* are exposed as {@link JsonToken#START_ARRAY} {@link JsonToken#END_ARRAY}) or not. If they are not
93-
* returned as `[]` tokens, they will be returned as {@link JsonToken#VALUE_STRING}
94-
* tokens with textual value of "" (empty String).
95-
*<p>
96-
* Default setting is {@code false}
97-
*
98-
* @since 2.9
99-
*/
100-
EMPTY_ELEMENT_AS_EMPTY_ARRAY(false),
101-
10291
/**
10392
* Feature that indicates whether XML Schema Instance attribute
10493
* {@code xsi:nil} will be processed automatically -- to indicate {@code null}
@@ -172,6 +161,8 @@ private Feature(boolean defaultState) {
172161
*/
173162
protected String _cfgNameForTextElement = DEFAULT_UNNAMED_TEXT_PROPERTY;
174163

164+
protected String _cfgValueForEmptyElement = DEFAULT_EMPTY_ELEMENT_VALUE;
165+
175166
/*
176167
/**********************************************************
177168
/* Configuration
@@ -289,17 +280,25 @@ private Feature(boolean defaultState) {
289280
*/
290281

291282
public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
292-
ObjectCodec codec, XMLStreamReader xmlReader, XmlNameProcessor tagProcessor)
283+
ObjectCodec codec, XMLStreamReader xmlReader, XmlNameProcessor tagProcessor)
284+
throws IOException
285+
{
286+
this(ctxt, genericParserFeatures, xmlFeatures, codec, xmlReader, tagProcessor, FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE);
287+
}
288+
289+
public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
290+
ObjectCodec codec, XMLStreamReader xmlReader, XmlNameProcessor tagProcessor, String valueForEmptyElement)
293291
throws IOException
294292
{
295293
super(genericParserFeatures);
294+
_cfgValueForEmptyElement = valueForEmptyElement;
296295
_formatFeatures = xmlFeatures;
297296
_ioContext = ctxt;
298297
_streamReadConstraints = ctxt.streamReadConstraints();
299298
_objectCodec = codec;
300299
_parsingContext = XmlReadContext.createRootContext(-1, -1);
301300
_xmlTokens = new XmlTokenStream(xmlReader, ctxt.contentReference(),
302-
_formatFeatures, tagProcessor);
301+
_formatFeatures, _cfgValueForEmptyElement, tagProcessor);
303302

304303
final int firstToken;
305304
try {
@@ -358,6 +357,13 @@ public void setXMLTextElementName(String name) {
358357
_cfgNameForTextElement = name;
359358
}
360359

360+
/**
361+
* @since 2.17
362+
*/
363+
public void setEmptyElementValue(String value) {
364+
_cfgValueForEmptyElement = value;
365+
}
366+
361367
/*
362368
/**********************************************************************
363369
/* Overrides: capability introspection methods

src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlTokenStream.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import javax.xml.XMLConstants;
66
import javax.xml.stream.*;
77

8-
import com.fasterxml.jackson.core.JsonToken;
98
import org.codehaus.stax2.XMLStreamLocation2;
109
import org.codehaus.stax2.XMLStreamReader2;
1110

@@ -121,6 +120,8 @@ public class XmlTokenStream
121120
*/
122121
protected String _textValue;
123122

123+
protected String _valueForEmptyElement;
124+
124125
/**
125126
* Marker flag set if caller wants to "push back" current token so
126127
* that next call to {@link #next()} should simply be given what was
@@ -169,6 +170,12 @@ public class XmlTokenStream
169170

170171
public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
171172
int formatFeatures, XmlNameProcessor nameProcessor)
173+
{
174+
this(xmlReader, sourceRef, formatFeatures, FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE, nameProcessor);
175+
}
176+
177+
public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
178+
int formatFeatures, String valueForEmptyElement, XmlNameProcessor nameProcessor)
172179
{
173180
_sourceReference = sourceRef;
174181
_formatFeatures = formatFeatures;
@@ -177,6 +184,7 @@ public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
177184
// 04-Dec-2023, tatu: [dataformat-xml#618] Need further customized adapter:
178185
_xmlReader = Stax2JacksonReaderAdapter.wrapIfNecessary(xmlReader);
179186
_nameProcessor = nameProcessor;
187+
_valueForEmptyElement = valueForEmptyElement;
180188
}
181189

182190
/**
@@ -561,10 +569,7 @@ private final String _collectUntilTag() throws XMLStreamException
561569
if (FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL.enabledIn(_formatFeatures)) {
562570
return null;
563571
}
564-
if (FromXmlParser.Feature.EMPTY_ELEMENT_AS_EMPTY_ARRAY.enabledIn(_formatFeatures)) {
565-
return JsonToken.START_ARRAY.asString() + JsonToken.END_ARRAY.asString();
566-
}
567-
return "";
572+
return _valueForEmptyElement;
568573
}
569574

570575
CharSequence chars = null;

src/test/java/com/fasterxml/jackson/dataformat/xml/deser/EmptyStringValueTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public void testEmptyElementEmptyArray() throws Exception
9797

9898
// but can be changed
9999
XmlMapper mapper2 = XmlMapper.builder()
100-
.enable(FromXmlParser.Feature.EMPTY_ELEMENT_AS_EMPTY_ARRAY)
100+
.valueForEmptyElement("[]")
101101
.build();
102102
name = mapper2.readValue(XML, Name.class);
103103
assertNotNull(name);

0 commit comments

Comments
 (0)