Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mechanism for processing invalid XML names (transforming to valid ones) #531

Merged
merged 1 commit into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions src/main/java/com/fasterxml/jackson/dataformat/xml/XmlFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ public class XmlFactory extends JsonFactory
protected transient XMLOutputFactory _xmlOutputFactory;

protected String _cfgNameForTextElement;

protected XmlTagProcessor _tagProcessor;

/*
/**********************************************************
Expand Down Expand Up @@ -102,11 +104,18 @@ public XmlFactory(ObjectCodec oc, XMLInputFactory xmlIn, XMLOutputFactory xmlOut
xmlIn, xmlOut, null);
}

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

protected XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
String nameForTextElem)
String nameForTextElem, XmlTagProcessor tagProcessor)
{
super(oc);
_tagProcessor = tagProcessor;
_xmlParserFeatures = xpFeatures;
_xmlGeneratorFeatures = xgFeatures;
_cfgNameForTextElement = nameForTextElem;
Expand Down Expand Up @@ -140,6 +149,7 @@ protected XmlFactory(XmlFactory src, ObjectCodec oc)
_cfgNameForTextElement = src._cfgNameForTextElement;
_xmlInputFactory = src._xmlInputFactory;
_xmlOutputFactory = src._xmlOutputFactory;
_tagProcessor = src._tagProcessor;
}

/**
Expand All @@ -155,6 +165,7 @@ protected XmlFactory(XmlFactoryBuilder b)
_cfgNameForTextElement = b.nameForTextElement();
_xmlInputFactory = b.xmlInputFactory();
_xmlOutputFactory = b.xmlOutputFactory();
_tagProcessor = b.xmlTagProcessor();
_initFactories(_xmlInputFactory, _xmlOutputFactory);
}

Expand Down Expand Up @@ -325,6 +336,14 @@ public int getFormatGeneratorFeatures() {
return _xmlGeneratorFeatures;
}

public XmlTagProcessor getXmlTagProcessor() {
return _tagProcessor;
}

public void setXmlTagProcessor(XmlTagProcessor _tagProcessor) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless something in setup requires this, let's leave out this mutator: being a new feature should be possible to just use Builder approach. 3.0 (master) will have to remove it otherwise.

this._tagProcessor = _tagProcessor;
}

/*
/******************************************************
/* Configuration, XML, generator settings
Expand Down Expand Up @@ -498,7 +517,7 @@ public ToXmlGenerator createGenerator(OutputStream out, JsonEncoding enc) throws
ctxt.setEncoding(enc);
return new ToXmlGenerator(ctxt,
_generatorFeatures, _xmlGeneratorFeatures,
_objectCodec, _createXmlWriter(ctxt, out));
_objectCodec, _createXmlWriter(ctxt, out), _tagProcessor);
}

@Override
Expand All @@ -507,7 +526,7 @@ public ToXmlGenerator createGenerator(Writer out) throws IOException
final IOContext ctxt = _createContext(_createContentReference(out), false);
return new ToXmlGenerator(ctxt,
_generatorFeatures, _xmlGeneratorFeatures,
_objectCodec, _createXmlWriter(ctxt, out));
_objectCodec, _createXmlWriter(ctxt, out), _tagProcessor);
}

@SuppressWarnings("resource")
Expand All @@ -519,7 +538,7 @@ public ToXmlGenerator createGenerator(File f, JsonEncoding enc) throws IOExcepti
final IOContext ctxt = _createContext(_createContentReference(out), true);
ctxt.setEncoding(enc);
return new ToXmlGenerator(ctxt, _generatorFeatures, _xmlGeneratorFeatures,
_objectCodec, _createXmlWriter(ctxt, out));
_objectCodec, _createXmlWriter(ctxt, out), _tagProcessor);
}

/*
Expand All @@ -543,7 +562,7 @@ public FromXmlParser createParser(XMLStreamReader sr) throws IOException

// false -> not managed
FromXmlParser xp = new FromXmlParser(_createContext(_createContentReference(sr), false),
_parserFeatures, _xmlParserFeatures, _objectCodec, sr);
_parserFeatures, _xmlParserFeatures, _objectCodec, sr, _tagProcessor);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -562,7 +581,7 @@ public ToXmlGenerator createGenerator(XMLStreamWriter sw) throws IOException
sw = _initializeXmlWriter(sw);
IOContext ctxt = _createContext(_createContentReference(sw), false);
return new ToXmlGenerator(ctxt, _generatorFeatures, _xmlGeneratorFeatures,
_objectCodec, sw);
_objectCodec, sw, _tagProcessor);
}

/*
Expand All @@ -582,7 +601,7 @@ protected FromXmlParser _createParser(InputStream in, IOContext ctxt) throws IOE
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_objectCodec, sr, _tagProcessor);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -600,7 +619,7 @@ protected FromXmlParser _createParser(Reader r, IOContext ctxt) throws IOExcepti
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_objectCodec, sr, _tagProcessor);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -627,7 +646,7 @@ protected FromXmlParser _createParser(char[] data, int offset, int len, IOContex
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_objectCodec, sr, _tagProcessor);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -651,7 +670,7 @@ protected FromXmlParser _createParser(byte[] data, int offset, int len, IOContex
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_objectCodec, sr, _tagProcessor);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public class XmlFactoryBuilder extends TSFBuilder<XmlFactory, XmlFactoryBuilder>
*/
protected ClassLoader _classLoaderForStax;

/**
* See {@link XmlTagProcessor} and {@link XmlTagProcessors}
*
* @since 2.14
*/
protected XmlTagProcessor _tagProcessor;

/*
/**********************************************************
/* Life cycle
Expand All @@ -73,6 +80,7 @@ protected XmlFactoryBuilder() {
_formatParserFeatures = XmlFactory.DEFAULT_XML_PARSER_FEATURE_FLAGS;
_formatGeneratorFeatures = XmlFactory.DEFAULT_XML_GENERATOR_FEATURE_FLAGS;
_classLoaderForStax = null;
_tagProcessor = XmlTagProcessors.newPassthroughProcessor();
}

public XmlFactoryBuilder(XmlFactory base) {
Expand All @@ -82,6 +90,7 @@ public XmlFactoryBuilder(XmlFactory base) {
_xmlInputFactory = base._xmlInputFactory;
_xmlOutputFactory = base._xmlOutputFactory;
_nameForTextElement = base._cfgNameForTextElement;
_tagProcessor = base._tagProcessor;
_classLoaderForStax = null;
}

Expand Down Expand Up @@ -133,6 +142,10 @@ protected ClassLoader staxClassLoader() {
getClass().getClassLoader() : _classLoaderForStax;
}

public XmlTagProcessor xmlTagProcessor() {
return _tagProcessor;
}

// // // Parser features

public XmlFactoryBuilder enable(FromXmlParser.Feature f) {
Expand Down Expand Up @@ -253,6 +266,14 @@ public XmlFactoryBuilder staxClassLoader(ClassLoader cl) {
_classLoaderForStax = cl;
return _this();
}

/**
* @since 2.14
*/
public XmlFactoryBuilder xmlTagProcessor(XmlTagProcessor tagProcessor) {
_tagProcessor = tagProcessor;
return _this();
}

// // // Actual construction

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/fasterxml/jackson/dataformat/xml/XmlMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ public Builder defaultUseWrapper(boolean state) {
_mapper.setDefaultUseWrapper(state);
return this;
}

/**
* @since 2.14
*/
public Builder xmlTagProcessor(XmlTagProcessor tagProcessor) {
_mapper.setXmlTagProcessor(tagProcessor);
return this;
}
}

protected final static JacksonXmlModule DEFAULT_XML_MODULE = new JacksonXmlModule();
Expand Down Expand Up @@ -280,6 +288,20 @@ public XmlMapper setDefaultUseWrapper(boolean state) {
return this;
}

/**
* @since 2.14
*/
public void setXmlTagProcessor(XmlTagProcessor tagProcessor) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also remove this mutator; should be passed via XmlFactory (and then avoids requiring the other setter in factory)

((XmlFactory)_jsonFactory).setXmlTagProcessor(tagProcessor);
}

/**
* @since 2.14
*/
public XmlTagProcessor getXmlTagProcessor() {
return ((XmlFactory)_jsonFactory).getXmlTagProcessor();
}

/*
/**********************************************************
/* Access to configuration settings
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.fasterxml.jackson.dataformat.xml;

import java.io.Serializable;

/**
* XML tag name processor primarily used for dealing with tag names
* containing invalid characters. Invalid characters in tags can,
* for instance, easily appear in map keys.
* <p>
* Processors should be set in the {@link XmlMapper#setXmlTagProcessor(XmlTagProcessor)}
* and/or the {@link XmlMapper.Builder#xmlTagProcessor(XmlTagProcessor)} methods.
* <p>
* See {@link XmlTagProcessors} for default processors.
*
* @since 2.14
*/
public interface XmlTagProcessor extends Serializable {

/**
* Representation of an XML tag name
*/
class XmlTagName {
public final String namespace;
public final String localPart;

public XmlTagName(String namespace, String localPart) {
this.namespace = namespace;
this.localPart = localPart;
}
}


/**
* Used during XML serialization.
* <p>
* This method should process the provided {@link XmlTagName} and
* escape / encode invalid XML characters.
*
* @param tag The tag to encode
* @return The encoded tag name
*/
XmlTagName encodeTag(XmlTagName tag);


/**
* Used during XML deserialization.
* <p>
* This method should process the provided {@link XmlTagName} and
* revert the encoding done in the {@link #encodeTag(XmlTagName)}
* method.
* <p>
* Note: Depending on the use case, it is not always required (or
* even possible) to reverse an encoding with 100% accuracy.
*
* @param tag The tag to encode
* @return The encoded tag name
*/
XmlTagName decodeTag(XmlTagName tag);

}
Loading