Skip to content

Commit

Permalink
Make empty values configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
Croway committed Feb 20, 2024
1 parent c11e8bc commit 371fda9
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 29 deletions.
42 changes: 34 additions & 8 deletions src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class XmlFactory extends JsonFactory

protected String _cfgNameForTextElement;

protected String _cfgValueForEmptyElement;

protected XmlNameProcessor _nameProcessor;

/*
Expand Down Expand Up @@ -107,18 +109,26 @@ public XmlFactory(ObjectCodec oc, XMLInputFactory xmlIn, XMLOutputFactory xmlOut
public XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
String nameForTextElem) {
this(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, XmlNameProcessors.newPassthroughProcessor());
this(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE, XmlNameProcessors.newPassthroughProcessor());
}

public XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
String nameForTextElem, String valueForEmptyElement) {
this(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, valueForEmptyElement,
XmlNameProcessors.newPassthroughProcessor());
}

protected XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
String nameForTextElem, XmlNameProcessor nameProcessor)
String nameForTextElem, String valueForEmptyElement, XmlNameProcessor nameProcessor)
{
super(oc);
_nameProcessor = nameProcessor;
_xmlParserFeatures = xpFeatures;
_xmlGeneratorFeatures = xgFeatures;
_cfgNameForTextElement = nameForTextElem;
_cfgValueForEmptyElement = valueForEmptyElement;
if (xmlIn == null) {
xmlIn = StaxUtil.defaultInputFactory(getClass().getClassLoader());
// as per [dataformat-xml#190], disable external entity expansion by default
Expand All @@ -145,6 +155,7 @@ protected XmlFactory(XmlFactory src, ObjectCodec oc)
_xmlParserFeatures = src._xmlParserFeatures;
_xmlGeneratorFeatures = src._xmlGeneratorFeatures;
_cfgNameForTextElement = src._cfgNameForTextElement;
_cfgValueForEmptyElement = src._cfgValueForEmptyElement;
_xmlInputFactory = src._xmlInputFactory;
_xmlOutputFactory = src._xmlOutputFactory;
_nameProcessor = src._nameProcessor;
Expand All @@ -161,6 +172,7 @@ protected XmlFactory(XmlFactoryBuilder b)
_xmlParserFeatures = b.formatParserFeaturesMask();
_xmlGeneratorFeatures = b.formatGeneratorFeaturesMask();
_cfgNameForTextElement = b.nameForTextElement();
_cfgValueForEmptyElement = b.valueForEmptyElement();
_xmlInputFactory = b.xmlInputFactory();
_xmlOutputFactory = b.xmlOutputFactory();
_nameProcessor = b.xmlNameProcessor();
Expand Down Expand Up @@ -237,7 +249,7 @@ protected Object readResolve() {
throw new IllegalArgumentException(e);
}
return new XmlFactory(_objectCodec, _xmlParserFeatures, _xmlGeneratorFeatures,
inf, outf, _cfgNameForTextElement);
inf, outf, _cfgNameForTextElement, _cfgValueForEmptyElement);
}

/**
Expand Down Expand Up @@ -281,6 +293,20 @@ public void setXMLTextElementName(String name) {
public String getXMLTextElementName() {
return _cfgNameForTextElement;
}

/**
* @since 2.17
*/
public void setEmptyElementValue(String value) {
_cfgValueForEmptyElement = value;
}

/**
* @since 2.17
*/
public String getEmptyElementValue() {
return _cfgValueForEmptyElement;
}

/*
/**********************************************************
Expand Down Expand Up @@ -560,7 +586,7 @@ public FromXmlParser createParser(XMLStreamReader sr) throws IOException

// false -> not managed
FromXmlParser xp = new FromXmlParser(_createContext(_createContentReference(sr), false),
_parserFeatures, _xmlParserFeatures, _objectCodec, sr, _nameProcessor);
_parserFeatures, _xmlParserFeatures, _objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand Down Expand Up @@ -599,7 +625,7 @@ protected FromXmlParser _createParser(InputStream in, IOContext ctxt) throws IOE
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr, _nameProcessor);
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -617,7 +643,7 @@ protected FromXmlParser _createParser(Reader r, IOContext ctxt) throws IOExcepti
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr, _nameProcessor);
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -644,7 +670,7 @@ protected FromXmlParser _createParser(char[] data, int offset, int len, IOContex
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr, _nameProcessor);
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand Down Expand Up @@ -678,7 +704,7 @@ protected FromXmlParser _createParser(byte[] data, int offset, int len, IOContex
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr, _nameProcessor);
_objectCodec, sr, _nameProcessor, _cfgValueForEmptyElement);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public class XmlFactoryBuilder extends TSFBuilder<XmlFactory, XmlFactoryBuilder>
*/
protected String _nameForTextElement;

/**
* Set a default value in case of empty an empty element (empty XML tag)
*<p>
* Value used for pseudo-property used for returning empty XML tag.
* Defaults to empty String, but may be changed.
*/
protected String _valueForEmptyElement = FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE;

/**
* Optional {@link ClassLoader} to use for constructing
* {@link XMLInputFactory} and {@kink XMLOutputFactory} instances if
Expand Down Expand Up @@ -91,6 +99,7 @@ public XmlFactoryBuilder(XmlFactory base) {
_xmlInputFactory = base._xmlInputFactory;
_xmlOutputFactory = base._xmlOutputFactory;
_nameForTextElement = base._cfgNameForTextElement;
_valueForEmptyElement = base._cfgValueForEmptyElement;
_nameProcessor = base._nameProcessor;
_classLoaderForStax = null;
}
Expand All @@ -102,6 +111,8 @@ public XmlFactoryBuilder(XmlFactory base) {

public String nameForTextElement() { return _nameForTextElement; }

public String valueForEmptyElement() { return _valueForEmptyElement; }

public XMLInputFactory xmlInputFactory() {
if (_xmlInputFactory == null) {
return defaultInputFactory();
Expand Down Expand Up @@ -213,6 +224,11 @@ public XmlFactoryBuilder nameForTextElement(String name) {
return _this();
}

public XmlFactoryBuilder valueForEmptyElement(String value) {
_valueForEmptyElement = value;
return _this();
}

/**
* @since 2.13 (was misnamed as {@code inputFactory(in) formerly})
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ public Builder nameForTextElement(String name) {
return this;
}

public Builder valueForEmptyElement(String value) {
_mapper.setValueForEmptyElement(value);
return this;
}

public Builder defaultUseWrapper(boolean state) {
_mapper.setDefaultUseWrapper(state);
return this;
Expand Down Expand Up @@ -271,6 +276,10 @@ protected void setXMLTextElementName(String name) {
getFactory().setXMLTextElementName(name);
}

protected void setValueForEmptyElement(String value) {
getFactory().setEmptyElementValue(value);
}

/**
* Since 2.7
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class FromXmlParser
*/
public final static String DEFAULT_UNNAMED_TEXT_PROPERTY = "";

public final static String DEFAULT_EMPTY_ELEMENT_VALUE = "";

/**
* XML format has some peculiarities, indicated via new (2.12) capability
* system.
Expand Down Expand Up @@ -86,19 +88,6 @@ public enum Feature implements FormatFeature
*/
EMPTY_ELEMENT_AS_NULL(false),

/**
* Feature that indicates whether XML Empty elements (ones where there are
* no separate start and end tags, but just one tag that ends with "/&gt;")
* are exposed as {@link JsonToken#START_ARRAY} {@link JsonToken#END_ARRAY}) or not. If they are not
* returned as `[]` tokens, they will be returned as {@link JsonToken#VALUE_STRING}
* tokens with textual value of "" (empty String).
*<p>
* Default setting is {@code false}
*
* @since 2.9
*/
EMPTY_ELEMENT_AS_EMPTY_ARRAY(false),

/**
* Feature that indicates whether XML Schema Instance attribute
* {@code xsi:nil} will be processed automatically -- to indicate {@code null}
Expand Down Expand Up @@ -172,6 +161,8 @@ private Feature(boolean defaultState) {
*/
protected String _cfgNameForTextElement = DEFAULT_UNNAMED_TEXT_PROPERTY;

protected String _cfgValueForEmptyElement = DEFAULT_EMPTY_ELEMENT_VALUE;

/*
/**********************************************************
/* Configuration
Expand Down Expand Up @@ -289,17 +280,25 @@ private Feature(boolean defaultState) {
*/

public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
ObjectCodec codec, XMLStreamReader xmlReader, XmlNameProcessor tagProcessor)
ObjectCodec codec, XMLStreamReader xmlReader, XmlNameProcessor tagProcessor)
throws IOException
{
this(ctxt, genericParserFeatures, xmlFeatures, codec, xmlReader, tagProcessor, FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE);
}

public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
ObjectCodec codec, XMLStreamReader xmlReader, XmlNameProcessor tagProcessor, String valueForEmptyElement)
throws IOException
{
super(genericParserFeatures);
_cfgValueForEmptyElement = valueForEmptyElement;
_formatFeatures = xmlFeatures;
_ioContext = ctxt;
_streamReadConstraints = ctxt.streamReadConstraints();
_objectCodec = codec;
_parsingContext = XmlReadContext.createRootContext(-1, -1);
_xmlTokens = new XmlTokenStream(xmlReader, ctxt.contentReference(),
_formatFeatures, tagProcessor);
_formatFeatures, _cfgValueForEmptyElement, tagProcessor);

final int firstToken;
try {
Expand Down Expand Up @@ -358,6 +357,13 @@ public void setXMLTextElementName(String name) {
_cfgNameForTextElement = name;
}

/**
* @since 2.17
*/
public void setEmptyElementValue(String value) {
_cfgValueForEmptyElement = value;
}

/*
/**********************************************************************
/* Overrides: capability introspection methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import javax.xml.XMLConstants;
import javax.xml.stream.*;

import com.fasterxml.jackson.core.JsonToken;
import org.codehaus.stax2.XMLStreamLocation2;
import org.codehaus.stax2.XMLStreamReader2;

Expand Down Expand Up @@ -121,6 +120,8 @@ public class XmlTokenStream
*/
protected String _textValue;

protected String _valueForEmptyElement;

/**
* Marker flag set if caller wants to "push back" current token so
* that next call to {@link #next()} should simply be given what was
Expand Down Expand Up @@ -169,6 +170,12 @@ public class XmlTokenStream

public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
int formatFeatures, XmlNameProcessor nameProcessor)
{
this(xmlReader, sourceRef, formatFeatures, FromXmlParser.DEFAULT_EMPTY_ELEMENT_VALUE, nameProcessor);
}

public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
int formatFeatures, String valueForEmptyElement, XmlNameProcessor nameProcessor)
{
_sourceReference = sourceRef;
_formatFeatures = formatFeatures;
Expand All @@ -177,6 +184,7 @@ public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
// 04-Dec-2023, tatu: [dataformat-xml#618] Need further customized adapter:
_xmlReader = Stax2JacksonReaderAdapter.wrapIfNecessary(xmlReader);
_nameProcessor = nameProcessor;
_valueForEmptyElement = valueForEmptyElement;
}

/**
Expand Down Expand Up @@ -561,10 +569,7 @@ private final String _collectUntilTag() throws XMLStreamException
if (FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL.enabledIn(_formatFeatures)) {
return null;
}
if (FromXmlParser.Feature.EMPTY_ELEMENT_AS_EMPTY_ARRAY.enabledIn(_formatFeatures)) {
return JsonToken.START_ARRAY.asString() + JsonToken.END_ARRAY.asString();
}
return "";
return _valueForEmptyElement;
}

CharSequence chars = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void testEmptyElementEmptyArray() throws Exception

// but can be changed
XmlMapper mapper2 = XmlMapper.builder()
.enable(FromXmlParser.Feature.EMPTY_ELEMENT_AS_EMPTY_ARRAY)
.valueForEmptyElement("[]")
.build();
name = mapper2.readValue(XML, Name.class);
assertNotNull(name);
Expand Down

0 comments on commit 371fda9

Please sign in to comment.