DOMSerializer.java
package tools.jackson.databind.ext;
import java.io.StringWriter;
import javax.xml.XMLConstants;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
import tools.jackson.core.*;
import tools.jackson.core.type.WritableTypeId;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.annotation.JacksonStdImpl;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.std.StdSerializer;
@JacksonStdImpl
public class DOMSerializer extends StdSerializer<Node>
{
protected final TransformerFactory transformerFactory;
public DOMSerializer() {
super(Node.class);
try {
transformerFactory = TransformerFactory.newInstance();
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// 22-Mar-2023, tatu: [databind#3837] add these 2 settings further
setTransformerFactoryAttribute(transformerFactory, XMLConstants.ACCESS_EXTERNAL_DTD, "");
setTransformerFactoryAttribute(transformerFactory, XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
} catch (Exception e) {
throw new IllegalStateException("Could not instantiate `TransformerFactory`: "+e.getMessage(), e);
}
}
@Override
public void serialize(Node value, JsonGenerator g, SerializationContext provider)
throws JacksonException
{
try {
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "no");
StreamResult result = new StreamResult(new StringWriter());
transformer.transform(new DOMSource(value), result);
g.writeString(result.getWriter().toString());
} catch (TransformerConfigurationException e) {
throw new IllegalStateException("Could not create XML Transformer for writing DOM `Node` value: "+e.getMessage(), e);
} catch (TransformerException e) {
provider.reportMappingProblem(e, "DOM `Node` value serialization failed: %s", e.getMessage());
}
}
@Override
public void serializeWithType(Node value, JsonGenerator g, SerializationContext ctxt,
TypeSerializer typeSer)
throws JacksonException
{
// 23-Jul-2026, tatu: [databind#6113] `Node` written as XML text in JSON String,
// so needs same handling as `StdScalarSerializer` (compare to
// `XMLGregorianCalendarSerializer` / [databind#3217])
// Also: must not use runtime type, as that is JDK-internal implementation
// class (like `com.sun.org.apache.xerces.internal.dom.DocumentImpl`); but
// cannot always use `Node` either, since that is not a subtype of `Document`
// and would fail on deserialization of `Document`-declared values
Class<?> typeForId = _typeIdClassFor(value);
WritableTypeId typeIdDef = typeSer.writeTypePrefix(g, ctxt,
typeSer.typeId(value, typeForId, JsonToken.VALUE_STRING));
serialize(value, g, ctxt);
typeSer.writeTypeSuffix(g, ctxt, typeIdDef);
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) {
// 23-Jul-2026, tatu: [databind#6113] `Node` is written as XML text in JSON
// String, so report as such (and not as "any format")
visitStringFormat(visitor, typeHint);
}
/**
* Helper method for finding the DOM interface to use as the Type Id for given
* value: switches on {@link Node#getNodeType()} since runtime classes are
* JDK-internal implementation types, and node types are mutually exclusive.
*<p>
* Must stay in sync with {@link DOMDeserializer#findDeserializer}, which documents
* the supported set and the fallback.
*
* @since 3.3
*/
private static Class<? extends Node> _typeIdClassFor(Node value)
{
switch (value.getNodeType()) {
case Node.DOCUMENT_NODE:
return Document.class;
case Node.ELEMENT_NODE:
return Element.class;
// NOTE: needs its own Type Id even though `CDATASection` is a subtype of
// `Text`, or it would be read back as a plain `Text` node
case Node.CDATA_SECTION_NODE:
return CDATASection.class;
case Node.TEXT_NODE:
return Text.class;
case Node.COMMENT_NODE:
return Comment.class;
case Node.PROCESSING_INSTRUCTION_NODE:
return ProcessingInstruction.class;
case Node.DOCUMENT_FRAGMENT_NODE:
return DocumentFragment.class;
default:
return Node.class;
}
}
private static void setTransformerFactoryAttribute(final TransformerFactory transformerFactory,
final String name, final Object value) {
try {
transformerFactory.setAttribute(name, value);
} catch (Exception e) {
System.err.println("[DOMSerializer] Failed to set TransformerFactory attribute: " + name);
}
}
}