Skip to content

Commit 1f7d83d

Browse files
authored
Add XML tag name proccessing support via XmlTagProcessor (#531)
This commit adds an extendable `XmlTagProcessor` that is used for escaping invalid characters in XML tag names. fixes #523 fixes #524
1 parent 8327cfd commit 1f7d83d

File tree

10 files changed

+498
-16
lines changed

10 files changed

+498
-16
lines changed

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

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public class XmlFactory extends JsonFactory
6565
protected transient XMLOutputFactory _xmlOutputFactory;
6666

6767
protected String _cfgNameForTextElement;
68+
69+
protected XmlTagProcessor _tagProcessor;
6870

6971
/*
7072
/**********************************************************
@@ -102,11 +104,18 @@ public XmlFactory(ObjectCodec oc, XMLInputFactory xmlIn, XMLOutputFactory xmlOut
102104
xmlIn, xmlOut, null);
103105
}
104106

107+
public XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
108+
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
109+
String nameForTextElem) {
110+
this(oc, xpFeatures, xgFeatures, xmlIn, xmlOut, nameForTextElem, XmlTagProcessors.newPassthroughProcessor());
111+
}
112+
105113
protected XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
106114
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
107-
String nameForTextElem)
115+
String nameForTextElem, XmlTagProcessor tagProcessor)
108116
{
109117
super(oc);
118+
_tagProcessor = tagProcessor;
110119
_xmlParserFeatures = xpFeatures;
111120
_xmlGeneratorFeatures = xgFeatures;
112121
_cfgNameForTextElement = nameForTextElem;
@@ -140,6 +149,7 @@ protected XmlFactory(XmlFactory src, ObjectCodec oc)
140149
_cfgNameForTextElement = src._cfgNameForTextElement;
141150
_xmlInputFactory = src._xmlInputFactory;
142151
_xmlOutputFactory = src._xmlOutputFactory;
152+
_tagProcessor = src._tagProcessor;
143153
}
144154

145155
/**
@@ -155,6 +165,7 @@ protected XmlFactory(XmlFactoryBuilder b)
155165
_cfgNameForTextElement = b.nameForTextElement();
156166
_xmlInputFactory = b.xmlInputFactory();
157167
_xmlOutputFactory = b.xmlOutputFactory();
168+
_tagProcessor = b.xmlTagProcessor();
158169
_initFactories(_xmlInputFactory, _xmlOutputFactory);
159170
}
160171

@@ -325,6 +336,14 @@ public int getFormatGeneratorFeatures() {
325336
return _xmlGeneratorFeatures;
326337
}
327338

339+
public XmlTagProcessor getXmlTagProcessor() {
340+
return _tagProcessor;
341+
}
342+
343+
public void setXmlTagProcessor(XmlTagProcessor _tagProcessor) {
344+
this._tagProcessor = _tagProcessor;
345+
}
346+
328347
/*
329348
/******************************************************
330349
/* Configuration, XML, generator settings
@@ -498,7 +517,7 @@ public ToXmlGenerator createGenerator(OutputStream out, JsonEncoding enc) throws
498517
ctxt.setEncoding(enc);
499518
return new ToXmlGenerator(ctxt,
500519
_generatorFeatures, _xmlGeneratorFeatures,
501-
_objectCodec, _createXmlWriter(ctxt, out));
520+
_objectCodec, _createXmlWriter(ctxt, out), _tagProcessor);
502521
}
503522

504523
@Override
@@ -507,7 +526,7 @@ public ToXmlGenerator createGenerator(Writer out) throws IOException
507526
final IOContext ctxt = _createContext(_createContentReference(out), false);
508527
return new ToXmlGenerator(ctxt,
509528
_generatorFeatures, _xmlGeneratorFeatures,
510-
_objectCodec, _createXmlWriter(ctxt, out));
529+
_objectCodec, _createXmlWriter(ctxt, out), _tagProcessor);
511530
}
512531

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

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

544563
// false -> not managed
545564
FromXmlParser xp = new FromXmlParser(_createContext(_createContentReference(sr), false),
546-
_parserFeatures, _xmlParserFeatures, _objectCodec, sr);
565+
_parserFeatures, _xmlParserFeatures, _objectCodec, sr, _tagProcessor);
547566
if (_cfgNameForTextElement != null) {
548567
xp.setXMLTextElementName(_cfgNameForTextElement);
549568
}
@@ -562,7 +581,7 @@ public ToXmlGenerator createGenerator(XMLStreamWriter sw) throws IOException
562581
sw = _initializeXmlWriter(sw);
563582
IOContext ctxt = _createContext(_createContentReference(sw), false);
564583
return new ToXmlGenerator(ctxt, _generatorFeatures, _xmlGeneratorFeatures,
565-
_objectCodec, sw);
584+
_objectCodec, sw, _tagProcessor);
566585
}
567586

568587
/*
@@ -582,7 +601,7 @@ protected FromXmlParser _createParser(InputStream in, IOContext ctxt) throws IOE
582601
}
583602
sr = _initializeXmlReader(sr);
584603
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
585-
_objectCodec, sr);
604+
_objectCodec, sr, _tagProcessor);
586605
if (_cfgNameForTextElement != null) {
587606
xp.setXMLTextElementName(_cfgNameForTextElement);
588607
}
@@ -600,7 +619,7 @@ protected FromXmlParser _createParser(Reader r, IOContext ctxt) throws IOExcepti
600619
}
601620
sr = _initializeXmlReader(sr);
602621
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
603-
_objectCodec, sr);
622+
_objectCodec, sr, _tagProcessor);
604623
if (_cfgNameForTextElement != null) {
605624
xp.setXMLTextElementName(_cfgNameForTextElement);
606625
}
@@ -627,7 +646,7 @@ protected FromXmlParser _createParser(char[] data, int offset, int len, IOContex
627646
}
628647
sr = _initializeXmlReader(sr);
629648
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
630-
_objectCodec, sr);
649+
_objectCodec, sr, _tagProcessor);
631650
if (_cfgNameForTextElement != null) {
632651
xp.setXMLTextElementName(_cfgNameForTextElement);
633652
}
@@ -651,7 +670,7 @@ protected FromXmlParser _createParser(byte[] data, int offset, int len, IOContex
651670
}
652671
sr = _initializeXmlReader(sr);
653672
FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
654-
_objectCodec, sr);
673+
_objectCodec, sr, _tagProcessor);
655674
if (_cfgNameForTextElement != null) {
656675
xp.setXMLTextElementName(_cfgNameForTextElement);
657676
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ public class XmlFactoryBuilder extends TSFBuilder<XmlFactory, XmlFactoryBuilder>
6363
*/
6464
protected ClassLoader _classLoaderForStax;
6565

66+
/**
67+
* See {@link XmlTagProcessor} and {@link XmlTagProcessors}
68+
*
69+
* @since 2.14
70+
*/
71+
protected XmlTagProcessor _tagProcessor;
72+
6673
/*
6774
/**********************************************************
6875
/* Life cycle
@@ -73,6 +80,7 @@ protected XmlFactoryBuilder() {
7380
_formatParserFeatures = XmlFactory.DEFAULT_XML_PARSER_FEATURE_FLAGS;
7481
_formatGeneratorFeatures = XmlFactory.DEFAULT_XML_GENERATOR_FEATURE_FLAGS;
7582
_classLoaderForStax = null;
83+
_tagProcessor = XmlTagProcessors.newPassthroughProcessor();
7684
}
7785

7886
public XmlFactoryBuilder(XmlFactory base) {
@@ -82,6 +90,7 @@ public XmlFactoryBuilder(XmlFactory base) {
8290
_xmlInputFactory = base._xmlInputFactory;
8391
_xmlOutputFactory = base._xmlOutputFactory;
8492
_nameForTextElement = base._cfgNameForTextElement;
93+
_tagProcessor = base._tagProcessor;
8594
_classLoaderForStax = null;
8695
}
8796

@@ -133,6 +142,10 @@ protected ClassLoader staxClassLoader() {
133142
getClass().getClassLoader() : _classLoaderForStax;
134143
}
135144

145+
public XmlTagProcessor xmlTagProcessor() {
146+
return _tagProcessor;
147+
}
148+
136149
// // // Parser features
137150

138151
public XmlFactoryBuilder enable(FromXmlParser.Feature f) {
@@ -253,6 +266,14 @@ public XmlFactoryBuilder staxClassLoader(ClassLoader cl) {
253266
_classLoaderForStax = cl;
254267
return _this();
255268
}
269+
270+
/**
271+
* @since 2.14
272+
*/
273+
public XmlFactoryBuilder xmlTagProcessor(XmlTagProcessor tagProcessor) {
274+
_tagProcessor = tagProcessor;
275+
return _this();
276+
}
256277

257278
// // // Actual construction
258279

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ public Builder defaultUseWrapper(boolean state) {
108108
_mapper.setDefaultUseWrapper(state);
109109
return this;
110110
}
111+
112+
/**
113+
* @since 2.14
114+
*/
115+
public Builder xmlTagProcessor(XmlTagProcessor tagProcessor) {
116+
_mapper.setXmlTagProcessor(tagProcessor);
117+
return this;
118+
}
111119
}
112120

113121
protected final static JacksonXmlModule DEFAULT_XML_MODULE = new JacksonXmlModule();
@@ -280,6 +288,20 @@ public XmlMapper setDefaultUseWrapper(boolean state) {
280288
return this;
281289
}
282290

291+
/**
292+
* @since 2.14
293+
*/
294+
public void setXmlTagProcessor(XmlTagProcessor tagProcessor) {
295+
((XmlFactory)_jsonFactory).setXmlTagProcessor(tagProcessor);
296+
}
297+
298+
/**
299+
* @since 2.14
300+
*/
301+
public XmlTagProcessor getXmlTagProcessor() {
302+
return ((XmlFactory)_jsonFactory).getXmlTagProcessor();
303+
}
304+
283305
/*
284306
/**********************************************************
285307
/* Access to configuration settings
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.fasterxml.jackson.dataformat.xml;
2+
3+
import java.io.Serializable;
4+
5+
/**
6+
* XML tag name processor primarily used for dealing with tag names
7+
* containing invalid characters. Invalid characters in tags can,
8+
* for instance, easily appear in map keys.
9+
* <p>
10+
* Processors should be set in the {@link XmlMapper#setXmlTagProcessor(XmlTagProcessor)}
11+
* and/or the {@link XmlMapper.Builder#xmlTagProcessor(XmlTagProcessor)} methods.
12+
* <p>
13+
* See {@link XmlTagProcessors} for default processors.
14+
*
15+
* @since 2.14
16+
*/
17+
public interface XmlTagProcessor extends Serializable {
18+
19+
/**
20+
* Representation of an XML tag name
21+
*/
22+
class XmlTagName {
23+
public final String namespace;
24+
public final String localPart;
25+
26+
public XmlTagName(String namespace, String localPart) {
27+
this.namespace = namespace;
28+
this.localPart = localPart;
29+
}
30+
}
31+
32+
33+
/**
34+
* Used during XML serialization.
35+
* <p>
36+
* This method should process the provided {@link XmlTagName} and
37+
* escape / encode invalid XML characters.
38+
*
39+
* @param tag The tag to encode
40+
* @return The encoded tag name
41+
*/
42+
XmlTagName encodeTag(XmlTagName tag);
43+
44+
45+
/**
46+
* Used during XML deserialization.
47+
* <p>
48+
* This method should process the provided {@link XmlTagName} and
49+
* revert the encoding done in the {@link #encodeTag(XmlTagName)}
50+
* method.
51+
* <p>
52+
* Note: Depending on the use case, it is not always required (or
53+
* even possible) to reverse an encoding with 100% accuracy.
54+
*
55+
* @param tag The tag to encode
56+
* @return The encoded tag name
57+
*/
58+
XmlTagName decodeTag(XmlTagName tag);
59+
60+
}

0 commit comments

Comments
 (0)