package org.exist.xquery.functions.fn; import org.exist.dom.QName; import org.exist.dom.memtree.DocumentBuilderReceiver; import org.exist.dom.memtree.DocumentImpl; import org.exist.dom.memtree.MemTreeBuilder; import org.exist.util.serializer.XQuerySerializer; import org.exist.xquery.*; import org.exist.xquery.util.SerializerUtils; import org.exist.xquery.value.*; import org.xml.sax.SAXException; import javax.xml.transform.OutputKeys; import java.io.IOException; import java.io.StringWriter; import java.util.Properties; public class FunSerialize extends BasicFunction { public final static FunctionSignature[] signatures = { new FunctionSignature( new QName("serialize", Function.BUILTIN_FUNCTION_NS, FnModule.PREFIX), "This function serializes the supplied input sequence $arg as described in XSLT and XQuery Serialization 3.0, returning the " + "serialized representation of the sequence as a string.", new SequenceType[] { new FunctionParameterSequenceType("args", Type.ITEM, Cardinality.ZERO_OR_MORE, "The node set to serialize") }, new FunctionParameterSequenceType("result", Type.STRING, Cardinality.EXACTLY_ONE, "the string containing the serialized node set.") ), new FunctionSignature( new QName("serialize", Function.BUILTIN_FUNCTION_NS, FnModule.PREFIX), "This function serializes the supplied input sequence $arg as described in XSLT and XQuery Serialization 3.0, returning the " + "serialized representation of the sequence as a string.", new SequenceType[] { new FunctionParameterSequenceType("args", Type.ITEM, Cardinality.ZERO_OR_MORE, "The node set to serialize"), new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The serialization parameters") }, new FunctionParameterSequenceType("result", Type.STRING, Cardinality.EXACTLY_ONE, "the string containing the serialized node set.") ) }; public FunSerialize(XQueryContext context, FunctionSignature signature) { super(context, signature); } @Override public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { final Properties outputProperties = new Properties(); if (getArgumentCount() == 2 && !args[1].isEmpty()) { SerializerUtils.getSerializationOptions(this, (NodeValue) args[1].itemAt(0), outputProperties); } try(final StringWriter writer = new StringWriter()) { final XQuerySerializer xqSerializer = new XQuerySerializer(context.getBroker(), outputProperties, writer); Sequence seq = args[0]; if (!xqSerializer.isJSON()) { seq = normalize(seq); } xqSerializer.serialize(seq); return new StringValue(writer.toString()); } catch (final IOException | SAXException e) { throw new XPathException(this, FnModule.SENR0001, e.getMessage()); } } /** * Sequence normalization as described in * http://www.w3.org/TR/xslt-xquery-serialization-30/#serdm * * @param input non-normalized sequence * @return normalized sequence * @throws XPathException */ protected Sequence normalize(Sequence input) throws XPathException { if (input.isEmpty()) // "If the sequence that is input to serialization is empty, create a sequence S1 that consists of a zero-length string." {return StringValue.EMPTY_STRING;} final ValueSequence temp = new ValueSequence(input.getItemCount()); for (final SequenceIterator i = input.iterate(); i.hasNext(); ) { final Item next = i.nextItem(); if (Type.subTypeOf(next.getType(), Type.NODE)) { if (next.getType() == Type.ATTRIBUTE || next.getType() == Type.NAMESPACE || next.getType() == Type.FUNCTION_REFERENCE) {throw new XPathException(this, FnModule.SENR0001, "It is an error if an item in the sequence to serialize is an attribute node or a namespace node.");} temp.add(next); } else { // atomic value Item last = null; if (!temp.isEmpty()) {last = temp.itemAt(temp.getItemCount() - 1);} if (last != null && last.getType() == Type.STRING) // "For each subsequence of adjacent strings in S2, copy a single string to the new sequence // equal to the values of the strings in the subsequence concatenated in order, each separated // by a single space." {((StringValue)last).append(" " + next.getStringValue());} else // "For each item in S1, if the item is atomic, obtain the lexical representation of the item by // casting it to an xs:string and copy the string representation to the new sequence;" {temp.add(new StringValue(next.getStringValue()));} } } context.pushDocumentContext(); try { final MemTreeBuilder builder = context.getDocumentBuilder(); final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(builder, true); for (final SequenceIterator i = temp.iterate(); i.hasNext(); ) { final Item next = i.nextItem(); if (Type.subTypeOf(next.getType(), Type.NODE)) { next.copyTo(context.getBroker(), receiver); } else { receiver.characters(next.getStringValue()); } } return (DocumentImpl)receiver.getDocument(); } catch (final SAXException e) { throw new XPathException(this, FnModule.SENR0001, e.getMessage()); } finally { context.popDocumentContext(); } } }