Skip to content

Commit 23d8a65

Browse files
authored
Implement #207: allow mapping custom prefixes for namespace URIs (#855)
1 parent 1e41752 commit 23d8a65

8 files changed

Lines changed: 379 additions & 22 deletions

File tree

release-notes/CREDITS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ Stefan Walter (@marvin9000)
4343
* Requested #150: Allow specifying DOCTYPE declaration (`<!DOCTYPE root ...>`) to write
4444
(3.2.0)
4545

46+
Joachim Lous (@jlous)
47+
* Requested #207: Allow custom prefixes for Namespaces
48+
(3.2.0)
49+
4650
Leonard Meyer (@LeonardMeyer)
4751
* Reported #247: `@JacksonXmlRootElement` does not enforce the local name during
4852
deserialization (add `XmlReadFeature.ENFORCE_ROOT_ELEMENT_NAME`)

release-notes/VERSION

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Version: 3.x (for earlier see VERSION-2.x)
2121
#192: Two wrapped lists with items of same name conflict
2222
(reported by @TeemuStenhammar)
2323
(fix by @cowtowncoder, w/ Claude code)
24+
#207: Allow custom prefixes for Namespaces
25+
(requested by Joachim L)
26+
(fix by @cowtowncoder, w/ Claude code)
2427
#247: `@JacksonXmlRootElement` does not enforce the local name during
2528
deserialization (add `XmlReadFeature.ENFORCE_ROOT_ELEMENT_NAME`)
2629
(reported by Leonard M)
@@ -76,6 +79,8 @@ Version: 3.x (for earlier see VERSION-2.x)
7679
polymorphic nested objects
7780
(reported by Vitor P)
7881
(fix by @cowtowncoder, w/ Claude code)
82+
#541: Allow specifying URI of the default namespace for root element
83+
(fix by @cowtowncoder, w/ Claude code)
7984
#561: Deserializing empty timestamp fields to `null` value doesn't
8085
work (instead becomes "empty", Epoch time)
8186
(reported by @silvestr85)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package tools.jackson.dataformat.xml.ser;
2+
3+
import javax.xml.stream.XMLStreamException;
4+
5+
import org.codehaus.stax2.XMLStreamWriter2;
6+
7+
import tools.jackson.dataformat.xml.util.ArgUtil;
8+
9+
/**
10+
* Entity representing binding from prefix to namespace URI, used
11+
* to determine prefix to use for given namespace URI (and dynamically
12+
* adding necessary declarations)
13+
*
14+
* @since 3.2
15+
*/
16+
public class NamespaceBinding
17+
implements XmlGeneratorWritable
18+
{
19+
private final String _prefix;
20+
21+
private final String _namespaceURI;
22+
23+
public NamespaceBinding(String prefix, String namespaceURI) {
24+
_prefix = ArgUtil.emptyToNull(prefix);
25+
_namespaceURI = ArgUtil.nonEmptyNonNull("namespaceURI", namespaceURI);
26+
}
27+
28+
@Override
29+
public void write(ToXmlGenerator xmlGen, XMLStreamWriter2 sw) throws XMLStreamException {
30+
if (_prefix == null) {
31+
sw.setDefaultNamespace(_namespaceURI);
32+
} else {
33+
sw.setPrefix(_prefix, _namespaceURI);
34+
}
35+
}
36+
}
Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,14 @@
11
package tools.jackson.dataformat.xml.ser;
22

3-
import javax.xml.stream.XMLStreamException;
4-
5-
import org.codehaus.stax2.XMLStreamWriter2;
6-
73
/**
8-
* Common API for XML nodes -- Comments, DTDs, Processing Instructions -- to
4+
* Base API for XML nodes -- Comments, DTDs, Processing Instructions -- to
95
* be written in Document Prolog (before actual XML Document (root node),
106
* after optional XML Declaration).
117
*
128
* @since 3.2
139
*/
1410
public interface PrologDirective
11+
extends XmlGeneratorWritable
1512
{
16-
/**
17-
* Method to call to actually write out the directive using given
18-
* {@link XMLStreamWriter2}. {@link ToXmlGenerator} is only passed
19-
* in case access to configuration was needed.
20-
*
21-
* @param xmlGen Generator that called this method: MUST NOT call
22-
* its output methods, only to be used for configuration access
23-
* @param sw Writer to use for actual output of XML event
24-
*/
25-
public void write(ToXmlGenerator xmlGen, XMLStreamWriter2 sw)
26-
throws XMLStreamException;
13+
// All we need is so far in XmlGeneratorWritable
2714
}

src/main/java/tools/jackson/dataformat/xml/ser/ToXmlGenerator.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ public class ToXmlGenerator
9999
/**********************************************************************
100100
*/
101101

102+
/**
103+
* Namespace bindings to add, if any.
104+
*
105+
* @since 3.2
106+
*/
107+
protected List<NamespaceBinding> _namespaceBindings;
108+
102109
/**
103110
* XML directives (DTD, Comments, PIs) to write, if any.
104111
*
@@ -228,16 +235,19 @@ public ToXmlGenerator(ObjectWriteContext writeCtxt, IOContext ioCtxt,
228235

229236
/**
230237
* Method called by {@link XmlGeneratorInitializer} to inject
231-
* necessary configuration.
238+
* necessary configuration like Prolog Directives and namespace
239+
* bindings.
232240
*
233241
* @since 3.2
234242
*/
235-
public void initProlog(boolean lfBetweenPrologDirectives,
236-
List<PrologDirective> directives)
243+
public void initDocument(boolean lfBetweenPrologDirectives,
244+
List<PrologDirective> directives,
245+
List<NamespaceBinding> nsBindings)
237246
{
238247
if (_initialized) { // sanity check
239-
_reportError("Internal error: cannot call `initConfig()` after generator already initialized");
248+
_reportError("Internal error: cannot call `initDocument()` after generator already initialized");
240249
}
250+
_namespaceBindings = nsBindings;
241251
_lfBetweenPrologDirectives = lfBetweenPrologDirectives;
242252
_prologDirectives = directives;
243253
}
@@ -285,7 +295,11 @@ public void initGenerator() throws JacksonException
285295
}
286296
}
287297
}
288-
298+
if (_namespaceBindings != null) {
299+
for (NamespaceBinding ns : _namespaceBindings) {
300+
ns.write(this, _xmlWriter);
301+
}
302+
}
289303
} catch (XMLStreamException e) {
290304
StaxUtil.throwAsWriteException(e, this);
291305
}

src/main/java/tools/jackson/dataformat/xml/ser/XmlGeneratorInitializer.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,25 @@
3131
public class XmlGeneratorInitializer
3232
implements GeneratorInitializer
3333
{
34+
/**
35+
* Prolog Directives to pass for generator to write.
36+
*/
3437
protected List<PrologDirective> _directives;
3538

39+
/**
40+
* Namespace bindings (prefix to URI) to register with generator.
41+
*/
42+
protected List<NamespaceBinding> _namespaceBindings;
43+
3644
protected boolean _addLfBetweenPrologDirectives = true;
3745

3846
protected boolean _hasDTD;
3947

4048
@Override
4149
public void initialize(SerializationConfig config, JsonGenerator g) throws JacksonException {
4250
if (g instanceof ToXmlGenerator xg) {
43-
xg.initProlog(_addLfBetweenPrologDirectives, _directives);
51+
xg.initDocument(_addLfBetweenPrologDirectives, _directives,
52+
_namespaceBindings);
4453
}
4554
}
4655

@@ -124,6 +133,39 @@ public XmlGeneratorInitializer addPI(String target, String data) {
124133
return _add(new PrologPI(target, data));
125134
}
126135

136+
/**
137+
* Method for specifying namespace URI to preferentially bind to the
138+
* "default namespace" (one used when element has no prefix).
139+
* This will guide underlying generator to add necessary
140+
* declarations when actually writing elements with matching
141+
* namespace URI.
142+
*
143+
* @param namespaceURI URI of the default namespace
144+
*
145+
* @return This initializer for call chaining
146+
*/
147+
public XmlGeneratorInitializer addDefaultNamespace(String namespaceURI) {
148+
return addNamespace(null, namespaceURI);
149+
}
150+
151+
/**
152+
* Method for adding a mapping (binding) between given prefix and matching
153+
* namespace URI. This will guide underlying generator to add necessary
154+
* declarations when actually writing namespaced attributes and elements.
155+
*
156+
* @param prefix Prefix to use for namespace
157+
* @param namespaceURI URI of the namespace
158+
*
159+
* @return This initializer for call chaining
160+
*/
161+
public XmlGeneratorInitializer addNamespace(String prefix, String namespaceURI) {
162+
if (_namespaceBindings == null) {
163+
_namespaceBindings = new ArrayList<>();
164+
}
165+
_namespaceBindings.add(new NamespaceBinding(prefix, namespaceURI));
166+
return this;
167+
}
168+
127169
protected XmlGeneratorInitializer _add(PrologDirective d) {
128170
if (_directives == null) {
129171
_directives = new ArrayList<>();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package tools.jackson.dataformat.xml.ser;
2+
3+
import javax.xml.stream.XMLStreamException;
4+
5+
import org.codehaus.stax2.XMLStreamWriter2;
6+
7+
/**
8+
* Common API for XML entities to write via {@link ToXmlGenerator}.
9+
*
10+
* @since 3.2
11+
*/
12+
public interface XmlGeneratorWritable
13+
{
14+
/**
15+
* Method to call to actually write out the entity using given
16+
* {@link XMLStreamWriter2}. {@link ToXmlGenerator} is only passed
17+
* in case access to configuration was needed.
18+
*
19+
* @param xmlGen Generator that called this method: MUST NOT call
20+
* its output methods, only to be used for configuration access
21+
* @param sw Writer to use for actual output of XML event
22+
*/
23+
public void write(ToXmlGenerator xmlGen, XMLStreamWriter2 sw)
24+
throws XMLStreamException;
25+
}

0 commit comments

Comments
 (0)