Skip to content

Commit

Permalink
Added support for multiple tag escaping strategies
Browse files Browse the repository at this point in the history
  • Loading branch information
mensinda committed Jun 7, 2022
1 parent 088e6bf commit e21f297
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.fasterxml.jackson.dataformat.xml;

/**
* Strategy for dealing with tags containing invalid characters. Invalid
* characters in tags can, for instance, easily appear in map keys.
*
* @since 2.14
*/
public enum TagEscapingStrategy {
/**
* Present for backwards compatibility. Using this option may produce
* invalid XML that can no longer be processed. For instance, this
* can easily happen for map keys containing special characters or spaces.
* <p>
* With this feature set, a map with the keys {@code "123"} and
* {@code "$ I am <fancy>! &;"} will be written as:
*
* <pre>{@code
* <DTO>
* <badMap>
* <$ I am <fancy>! &;>xyz</$ I am <fancy>! &;>
* <123>bar</123>
* </badMap>
* </DTO>
* }</pre>
*
* <b>Note:</b> This option ignored for deserialization, since even
* Jackson can't parse invalid XML.
*
* @since 2.14
*/
NONE,

/**
* Replaces all invalid characters in a tag with a {@code _}. Using this
* option will produce valid XML, but the generated XML will not be
* 100% reversible (as in serializing and deserializing an object may not
* produce the same object again).
* <p>
* With this feature set, a map with the keys {@code "123"} and
* {@code "$ I am <fancy>! &;"} will be written as:
*
* <pre>{@code
* <DTO>
* <badMap>
* <__I_am__fancy_____>xyz</__I_am__fancy_____>
* <_23>bar</_23>
* </badMap>
* </DTO>
* }</pre>
*
* <b>Note:</b> This option ignored for deserialization, since there is no
* way to reverse this step.
*
* @since 2.14
*/
REPLACE,

/**
* Strategy that indicates that invalid XML tag names should be escaped
* via an attribute ({@code real_name}) in a standard tag.
* <p>
* With this feature set, a map with the keys {@code "123"} and
* {@code "$ I am <fancy>! &;"} will be written as:
*
* <pre>{@code
* <DTO>
* <badMap>
* <escaped_tag real_name="$ I am &lt;fancy>! &amp;;">xyz</escaped_tag>
* <escaped_tag real_name="123">bar</escaped_tag>
* </badMap>
* </DTO>
* }</pre>
*
* @since 2.14
*/
ATTRIBUTE_ESCAPE,

/**
* With this strategy the entire tag is escaped via base64 with the prefix
* {@code base64_tag_}. Here the
* <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5">base64url</a>
* encoder and decoders are used. The {@code =} padding characters are
* always omitted.
* <p>
* With this feature set, a map with the keys {@code "123"} and
* {@code "$ I am <fancy>! &;"} will be written as:
*
* <pre>{@code
* <DTO>
* <badMap>
* <base64_tag_JCBJIGFtIDxmYW5jeT4hICY7>xyz</base64_tag_JCBJIGFtIDxmYW5jeT4hICY7>
* <base64_tag_MTIz>bar</base64_tag_MTIz>
* </badMap>
* </DTO>
* }</pre>
*
* @since 2.14
*/
BASE64,

;

public static final String ESCAPED_TAG_NAME = "escaped_tag";
public static final String ESCAPED_ATTR_NS = ""; // "jackson";
public static final String ESCAPED_ATTR_NAME = "real_name";
public static final String ESCAPED_BASE64_PREFIX = "base64_tag_";
}
45 changes: 36 additions & 9 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,10 @@ public class XmlFactory extends JsonFactory
protected transient XMLOutputFactory _xmlOutputFactory;

protected String _cfgNameForTextElement;

protected TagEscapingStrategy _serializationEscapeStrategy = TagEscapingStrategy.NONE;

protected TagEscapingStrategy _deserializationEscapeStrategy = TagEscapingStrategy.NONE;

/*
/**********************************************************
Expand Down Expand Up @@ -325,6 +329,17 @@ public int getFormatGeneratorFeatures() {
return _xmlGeneratorFeatures;
}

/**
* Set the serialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public XmlFactory serializationTagEscapingStrategy(TagEscapingStrategy strategy) {
_serializationEscapeStrategy = strategy;
return this;
}

/*
/******************************************************
/* Configuration, XML, generator settings
Expand Down Expand Up @@ -366,6 +381,18 @@ public final boolean isEnabled(ToXmlGenerator.Feature f) {
return (_xmlGeneratorFeatures & f.getMask()) != 0;
}


/**
* Set the seserialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public XmlFactory deserializationTagEscapingStrategy(TagEscapingStrategy strategy) {
_deserializationEscapeStrategy = strategy;
return this;
}

/*
/**********************************************************
/* Additional configuration
Expand Down Expand Up @@ -497,7 +524,7 @@ public ToXmlGenerator createGenerator(OutputStream out, JsonEncoding enc) throws
final IOContext ctxt = _createContext(_createContentReference(out), false);
ctxt.setEncoding(enc);
return new ToXmlGenerator(ctxt,
_generatorFeatures, _xmlGeneratorFeatures,
_generatorFeatures, _xmlGeneratorFeatures, _serializationEscapeStrategy,
_objectCodec, _createXmlWriter(ctxt, out));
}

Expand All @@ -506,7 +533,7 @@ public ToXmlGenerator createGenerator(Writer out) throws IOException
{
final IOContext ctxt = _createContext(_createContentReference(out), false);
return new ToXmlGenerator(ctxt,
_generatorFeatures, _xmlGeneratorFeatures,
_generatorFeatures, _xmlGeneratorFeatures, _serializationEscapeStrategy,
_objectCodec, _createXmlWriter(ctxt, out));
}

Expand All @@ -519,7 +546,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));
_serializationEscapeStrategy, _objectCodec, _createXmlWriter(ctxt, out));
}

/*
Expand All @@ -543,7 +570,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, _deserializationEscapeStrategy, _objectCodec, sr);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -562,7 +589,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);
_serializationEscapeStrategy, _objectCodec, sw);
}

/*
Expand All @@ -582,7 +609,7 @@ protected FromXmlParser _createParser(InputStream in, IOContext ctxt) throws IOE
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_deserializationEscapeStrategy, _objectCodec, sr);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -600,7 +627,7 @@ protected FromXmlParser _createParser(Reader r, IOContext ctxt) throws IOExcepti
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_deserializationEscapeStrategy, _objectCodec, sr);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -627,7 +654,7 @@ protected FromXmlParser _createParser(char[] data, int offset, int len, IOContex
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_deserializationEscapeStrategy, _objectCodec, sr);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand All @@ -651,7 +678,7 @@ protected FromXmlParser _createParser(byte[] data, int offset, int len, IOContex
}
sr = _initializeXmlReader(sr);
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
_objectCodec, sr);
_deserializationEscapeStrategy, _objectCodec, sr);
if (_cfgNameForTextElement != null) {
xp.setXMLTextElementName(_cfgNameForTextElement);
}
Expand Down
68 changes: 68 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,40 @@ public Builder defaultUseWrapper(boolean state) {
_mapper.setDefaultUseWrapper(state);
return this;
}

/**
* Set the serialization and deserialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public Builder tagEscapingStrategy(TagEscapingStrategy strategy) {
serializationTagEscapingStrategy(strategy);
deserializationTagEscapingStrategy(strategy);
return this;
}

/**
* Set the serialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public Builder serializationTagEscapingStrategy(TagEscapingStrategy strategy) {
_mapper.serializationTagEscapingStrategy(strategy);
return this;
}

/**
* Set the deserialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public Builder deserializationTagEscapingStrategy(TagEscapingStrategy strategy) {
_mapper.deserializationTagEscapingStrategy(strategy);
return this;
}
}

protected final static JacksonXmlModule DEFAULT_XML_MODULE = new JacksonXmlModule();
Expand Down Expand Up @@ -321,6 +355,40 @@ public ObjectMapper disable(FromXmlParser.Feature f) {
return this;
}

/**
* Set the serialization and deserialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public ObjectMapper tagEscapingStrategy(TagEscapingStrategy strategy) {
serializationTagEscapingStrategy(strategy);
deserializationTagEscapingStrategy(strategy);
return this;
}

/**
* Set the serialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public ObjectMapper serializationTagEscapingStrategy(TagEscapingStrategy strategy) {
((XmlFactory)_jsonFactory).serializationTagEscapingStrategy(strategy);
return this;
}

/**
* Set the seserialization strategy. See
* {@link TagEscapingStrategy} for more information.
*
* @since 2.14
*/
public ObjectMapper deserializationTagEscapingStrategy(TagEscapingStrategy strategy) {
((XmlFactory)_jsonFactory).deserializationTagEscapingStrategy(strategy);
return this;
}

/*
/**********************************************************
/* XML-specific access
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import com.fasterxml.jackson.core.util.JacksonFeatureSet;

import com.fasterxml.jackson.dataformat.xml.PackageVersion;
import com.fasterxml.jackson.dataformat.xml.TagEscapingStrategy;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import com.fasterxml.jackson.dataformat.xml.util.CaseInsensitiveNameSet;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;

Expand Down Expand Up @@ -83,15 +83,6 @@ public enum Feature implements FormatFeature
*/
PROCESS_XSI_NIL(true),

/**
* Feature that controls whether the escaping mechanism from the
* {@code ESCAPE_MALFORMED_TAGS} from {@link ToXmlGenerator.Feature}
* is reversed.
*
* @since 2.14
*/
PROCESS_ESCAPED_MALFORMED_TAGS(true)

// 16-Nov-2020, tatu: would have been nice to add in 2.12 but is not
// trivial to implement... so leaving out for now

Expand Down Expand Up @@ -262,7 +253,7 @@ private Feature(boolean defaultState) {
*/

public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
ObjectCodec codec, XMLStreamReader xmlReader)
TagEscapingStrategy tagEscapingStrategy, ObjectCodec codec, XMLStreamReader xmlReader)
throws IOException
{
super(genericParserFeatures);
Expand All @@ -271,7 +262,7 @@ public FromXmlParser(IOContext ctxt, int genericParserFeatures, int xmlFeatures,
_objectCodec = codec;
_parsingContext = XmlReadContext.createRootContext(-1, -1);
_xmlTokens = new XmlTokenStream(xmlReader, ctxt.contentReference(),
_formatFeatures);
_formatFeatures, tagEscapingStrategy);

final int firstToken;
try {
Expand Down
Loading

0 comments on commit e21f297

Please sign in to comment.