package org.xbib.elasticsearch.common.xcontent.xml; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.xcontent.XContentGenerator; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentString; import javax.xml.namespace.QName; import java.io.IOException; import java.io.InputStream; /** * * Content generator for XML format * */ public class XmlXContentGenerator implements XContentGenerator { private final static ESLogger logger = ESLoggerFactory.getLogger(XmlXContentGenerator.class.getName()); protected final ToXmlGenerator generator; private XmlXParams params; private boolean started; private boolean context; private String prefix; public XmlXContentGenerator(ToXmlGenerator generator) { this.generator = generator; this.params = new XmlXParams(); this.started = false; this.context = false; this.prefix = null; generator.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, false); } public XmlXContentGenerator setParams(XmlXParams params) { this.params = params; return this; } public XmlNamespaceContext getNamespaceContext() { return params.getNamespaceContext(); } @Override public XContentType contentType() { //return XmlXContentType.XML; return null; } @Override public void usePrettyPrint() { generator.useDefaultPrettyPrinter(); } @Override public void usePrintLineFeedAtEnd() { // nothing here } @Override public void writeStartArray() throws IOException { generator.writeStartArray(); } @Override public void writeEndArray() throws IOException { generator.writeEndArray(); } @Override public void writeStartObject() throws IOException { try { if (!started) { generator.getStaxWriter().setDefaultNamespace(params.getQName().getNamespaceURI()); generator.startWrappedValue(null, params.getQName()); } generator.writeStartObject(); if (!started ) { if (getNamespaceContext() != null && getNamespaceContext().getNamespaces() != null) { for (String prefix : getNamespaceContext().getNamespaces().keySet()) { generator.getStaxWriter().writeNamespace(prefix, getNamespaceContext().getNamespaceURI(prefix)); } } started = true; } } catch (Exception e) { logger.warn(e.getMessage(), e); } } @Override public void writeEndObject() throws IOException { generator.writeEndObject(); context = false; } @Override public void writeFieldName(String name) throws IOException { writeFieldNameXml(name); } @Override public void writeFieldName(XContentString name) throws IOException { writeFieldNameXml(name.getValue()); } @Override public void writeString(String text) throws IOException { try { generator.writeString(text); if (context && prefix != null) { params.getNamespaceContext().addNamespace(prefix, text); generator.getStaxWriter().writeNamespace(prefix, text); prefix = null; } } catch (Exception e) { logger.warn(e.getMessage() + ": " + text, e); } } @Override public void writeString(char[] text, int offset, int len) throws IOException { String s = new String(text, offset, len); try { generator.writeString(s); if (context && prefix != null) { params.getNamespaceContext().addNamespace(prefix, s); generator.getStaxWriter().writeNamespace(prefix, s); prefix = null; } } catch (Exception e) { logger.warn(e.getMessage() + ": " + s, e); } } @Override public void writeUTF8String(byte[] text, int offset, int length) throws IOException { String s = new String(text, offset, length); try { generator.writeUTF8String(text, offset, length); if (context && prefix != null) { params.getNamespaceContext().addNamespace(prefix, s); generator.getStaxWriter().writeNamespace(prefix, s); prefix = null; } } catch (Exception e) { logger.warn(e.getMessage() + ": " + s, e); } } @Override public void writeBinary(byte[] data, int offset, int len) throws IOException { generator.writeBinary(data, offset, len); } @Override public void writeBinary(byte[] data) throws IOException { generator.writeBinary(data); } @Override public void writeNumber(int v) throws IOException { generator.writeNumber(v); } @Override public void writeNumber(long v) throws IOException { generator.writeNumber(v); } @Override public void writeNumber(double d) throws IOException { generator.writeNumber(d); } @Override public void writeNumber(float f) throws IOException { generator.writeNumber(f); } @Override public void writeBoolean(boolean state) throws IOException { generator.writeBoolean(state); } @Override public void writeBooleanField(XContentString fieldName, boolean value) throws IOException { writeFieldName(fieldName); generator.writeBoolean(value); } @Override public void writeNull() throws IOException { generator.writeNull(); } @Override public void writeNullField(XContentString fieldName) throws IOException { writeFieldName(fieldName); generator.writeNull(); } @Override public void writeStringField(String fieldName, String value) throws IOException { try { generator.writeStringField(fieldName, value); if (context && value != null) { params.getNamespaceContext().addNamespace(fieldName, value); generator.getStaxWriter().writeNamespace(fieldName, value); } } catch (Exception e) { logger.warn(e.getMessage() + ": " + fieldName + "=" + value, e); } } @Override public void writeStringField(XContentString fieldName, String value) throws IOException { writeFieldName(fieldName); generator.writeString(value); } @Override public void writeBooleanField(String fieldName, boolean value) throws IOException { generator.writeBooleanField(fieldName, value); } @Override public void writeNullField(String fieldName) throws IOException { generator.writeNullField(fieldName); } @Override public void writeNumberField(String fieldName, int value) throws IOException { generator.writeNumberField(fieldName, value); } @Override public void writeNumberField(XContentString fieldName, int value) throws IOException { writeFieldName(fieldName); generator.writeNumber(value); } @Override public void writeNumberField(String fieldName, long value) throws IOException { generator.writeNumberField(fieldName, value); } @Override public void writeNumberField(XContentString fieldName, long value) throws IOException { writeFieldName(fieldName); generator.writeNumber(value); } @Override public void writeNumberField(String fieldName, double value) throws IOException { generator.writeNumberField(fieldName, value); } @Override public void writeNumberField(String fieldName, float value) throws IOException { generator.writeNumberField(fieldName, value); } @Override public void writeBinaryField(String fieldName, byte[] data) throws IOException { generator.writeBinaryField(fieldName, data); } @Override public void writeBinaryField(XContentString fieldName, byte[] value) throws IOException { writeFieldName(fieldName); generator.writeBinary(value); } @Override public void writeNumberField(XContentString fieldName, double value) throws IOException { writeFieldName(fieldName); generator.writeNumber(value); } @Override public void writeNumberField(XContentString fieldName, float value) throws IOException { writeFieldName(fieldName); generator.writeNumber(value); } public void writeArrayFieldStart(String fieldName) throws IOException { generator.writeArrayFieldStart(fieldName); } @Override public void writeArrayFieldStart(XContentString fieldName) throws IOException { writeFieldName(fieldName); generator.writeStartArray(); } public void writeObjectFieldStart(String fieldName) throws IOException { generator.writeObjectFieldStart(fieldName); } @Override public void writeObjectFieldStart(XContentString fieldName) throws IOException { writeFieldName(fieldName); generator.writeStartObject(); } @Override public void writeRawField(String fieldName, InputStream content) throws IOException { writeFieldNameXml(fieldName); try (JsonParser parser = XmlXContent.xmlFactory().createParser(content)) { parser.nextToken(); generator.copyCurrentStructure(parser); } } @Override public void writeRawField(String fieldName, BytesReference content) throws IOException { writeFieldNameXml(fieldName); try (JsonParser parser = XmlXContent.xmlFactory().createParser(content.toBytes())) { parser.nextToken(); generator.copyCurrentStructure(parser); } } @Override public void writeRawValue(BytesReference content) throws IOException { generator.writeRawValue(content.toUtf8()); } @Override public void copyCurrentStructure(XContentParser parser) throws IOException { if (parser.currentToken() == null) { parser.nextToken(); } if (parser instanceof XmlXContentParser) { generator.copyCurrentStructure(((XmlXContentParser) parser).parser); } else { copyCurrentStructure(this, parser); } } public static void copyCurrentStructure(XContentGenerator generator, XContentParser parser) throws IOException { XContentParser.Token t = parser.currentToken(); // Let's handle field-name separately first if (t == XContentParser.Token.FIELD_NAME) { generator.writeFieldName(parser.currentName()); t = parser.nextToken(); // fall-through to copy the associated value } switch (t) { case START_ARRAY: generator.writeStartArray(); while (parser.nextToken() != XContentParser.Token.END_ARRAY) { copyCurrentStructure(generator, parser); } generator.writeEndArray(); break; case START_OBJECT: generator.writeStartObject(); while (parser.nextToken() != XContentParser.Token.END_OBJECT) { copyCurrentStructure(generator, parser); } generator.writeEndObject(); break; default: // others are simple: copyCurrentEvent(generator, parser); } } public static void copyCurrentEvent(XContentGenerator generator, XContentParser parser) throws IOException { switch (parser.currentToken()) { case START_OBJECT: generator.writeStartObject(); break; case END_OBJECT: generator.writeEndObject(); break; case START_ARRAY: generator.writeStartArray(); break; case END_ARRAY: generator.writeEndArray(); break; case FIELD_NAME: generator.writeFieldName(parser.currentName()); break; case VALUE_STRING: if (parser.hasTextCharacters()) { generator.writeString(parser.textCharacters(), parser.textOffset(), parser.textLength()); } else { generator.writeString(parser.text()); } break; case VALUE_NUMBER: switch (parser.numberType()) { case INT: generator.writeNumber(parser.intValue()); break; case LONG: generator.writeNumber(parser.longValue()); break; case FLOAT: generator.writeNumber(parser.floatValue()); break; case DOUBLE: generator.writeNumber(parser.doubleValue()); break; } break; case VALUE_BOOLEAN: generator.writeBoolean(parser.booleanValue()); break; case VALUE_NULL: generator.writeNull(); break; case VALUE_EMBEDDED_OBJECT: generator.writeBinary(parser.binaryValue()); } } @Override public void flush() throws IOException { generator.flush(); } @Override public void close() throws IOException { generator.close(); } private void writeFieldNameXml(String name) throws IOException { if (!context) { this.context = "@context".equals(name); this.prefix = null; } if (name.startsWith("@")) { // setting to attribute is simple but tricky, it allows to declare namespaces in StaX generator.setNextIsAttribute(true); } else if (context) { prefix = name; } QName qname = toQName(name); generator.setNextName(qname); generator.writeFieldName(qname.getLocalPart()); } private QName toQName(String name) throws IOException { QName root = params.getQName(); XmlNamespaceContext context = params.getNamespaceContext(); String nsPrefix = root.getPrefix(); String nsURI = root.getNamespaceURI(); if (name.startsWith("_") || name.startsWith("@")) { name = name.substring(1); } name = ISO9075.encode(name); int pos = name.indexOf(':'); if (pos > 0) { nsPrefix = name.substring(0, pos); nsURI = context != null ? context.getNamespaceURI(nsPrefix) : XmlXParams.DEFAULT_ROOT.getNamespaceURI(); if (nsURI == null) { throw new IOException("unknown namespace prefix: " + nsPrefix); } name = name.substring(pos + 1); } return new QName(nsURI, name, nsPrefix); } }