2828import org .exist .storage .serializers .EXistOutputKeys ;
2929import org .exist .util .serializer .XQuerySerializer ;
3030import org .exist .xquery .*;
31+ import org .exist .xquery .functions .array .ArrayType ;
3132import org .exist .xquery .functions .map .AbstractMapType ;
3233import org .exist .xquery .util .SerializerUtils ;
3334import org .exist .xquery .value .*;
@@ -82,13 +83,16 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathExce
8283 try (final StringWriter writer = new StringWriter ()) {
8384 final XQuerySerializer xqSerializer = new XQuerySerializer (context .getBroker (), outputProperties , writer );
8485
85- Sequence seq = args [0 ];
86+ final Sequence input = args [0 ];
8687 if (xqSerializer .normalize ()) {
8788 final String itemSeparator = outputProperties .getProperty (EXistOutputKeys .ITEM_SEPARATOR , DEFAULT_ITEM_SEPARATOR );
88- seq = normalize (this , context , seq , itemSeparator );
89+ final Sequence normalized = normalize (this , context , input , itemSeparator );
90+ xqSerializer .serialize (normalized );
91+ }
92+ else {
93+ xqSerializer .serialize (input );
8994 }
9095
91- xqSerializer .serialize (seq );
9296 return new StringValue (this , writer .toString ());
9397 } catch (final IOException | SAXException e ) {
9498 throw new XPathException (this , FnModule .SENR0001 , e .getMessage ());
@@ -97,9 +101,9 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathExce
97101
98102 public static Properties getSerializationProperties (final Expression callingExpr , final Item parametersItem ) throws XPathException {
99103 final Properties outputProperties ;
100- if (parametersItem .getType () == Type .MAP ) {
104+ if (parametersItem .getType () == Type .MAP ) {
101105 outputProperties = SerializerUtils .getSerializationOptions (callingExpr , (AbstractMapType ) parametersItem );
102- } else if (isSerializationParametersElement (parametersItem )) {
106+ } else if (isSerializationParametersElement (parametersItem )) {
103107 outputProperties = new Properties ();
104108 SerializerUtils .getSerializationOptions (callingExpr , (NodeValue ) parametersItem , outputProperties );
105109 } else {
@@ -141,48 +145,59 @@ private static boolean isSerializationParametersElement(final Item item) {
141145 * @throws XPathException in case of dynamic error.
142146 */
143147 public static Sequence normalize (final Expression callingExpr , final XQueryContext context , final Sequence input , final String itemSeparator ) throws XPathException {
144- if (input .isEmpty ())
145- // "If the sequence that is input to serialization is empty, create a sequence S1 that consists of a zero-length string."
146- {return StringValue .EMPTY_STRING ;}
147- final ValueSequence temp = new ValueSequence (input .getItemCount ());
148+ // "If the sequence that is input to serialization is empty, create a sequence S1 that consists of a zero-length string."
149+ if (input .isEmpty ()) {
150+ return StringValue .EMPTY_STRING ;
151+ }
152+ // flatten arrays
153+ final ValueSequence step1 = new ValueSequence ();
148154 for (final SequenceIterator i = input .iterate (); i .hasNext (); ) {
149155 final Item next = i .nextItem ();
150- if (Type .subTypeOf (next .getType (), Type .NODE )) {
151- if (next .getType () == Type .ATTRIBUTE || next .getType () == Type .NAMESPACE || next .getType () == Type .FUNCTION_REFERENCE )
152- {throw new XPathException (callingExpr , FnModule .SENR0001 ,
153- "It is an error if an item in the sequence to serialize is an attribute node or a namespace node." );}
154- if (itemSeparator != null && itemSeparator .length () > 0 && !temp .isEmpty ()) {
155- temp .add (new StringValue (itemSeparator + next .getStringValue ()));
156- } else {
157- temp .add (next );
156+ if (next .getType () == Type .ARRAY ) {
157+ final Sequence sequence = ArrayType .flatten (next );
158+ for (final SequenceIterator si = sequence .iterate (); si .hasNext (); ) {
159+ step1 .add (si .nextItem ());
160+ }
161+ } else {
162+ step1 .add (next );
163+ }
164+ }
165+
166+ final ValueSequence step2 = new ValueSequence (step1 .getItemCount ());
167+ for (final SequenceIterator i = step1 .iterate (); i .hasNext (); ) {
168+ final Item next = i .nextItem ();
169+ final int itemType = next .getType ();
170+ if (Type .subTypeOf (itemType , Type .NODE )) {
171+ if (itemType == Type .ATTRIBUTE || itemType == Type .NAMESPACE || itemType == Type .FUNCTION_REFERENCE ) {
172+ throw new XPathException (callingExpr , FnModule .SENR0001 ,
173+ "It is an error if an item in the sequence to serialize is an attribute node or a namespace node." );
158174 }
175+ step2 .add (next );
159176 } else {
160177 // atomic value
161- Item last = null ;
162- if (!temp .isEmpty ())
163- {last = temp .itemAt (temp .getItemCount () - 1 );}
164- if (last != null && last .getType () == Type .STRING )
165- // "For each subsequence of adjacent strings in S2, copy a single string to the new sequence
166- // equal to the values of the strings in the subsequence concatenated in order, each separated
167- // by a single space."
168- {((StringValue )last ).append ((itemSeparator == null ? " " : itemSeparator ) + next .getStringValue ());}
169- else
170- // "For each item in S1, if the item is atomic, obtain the lexical representation of the item by
171- // casting it to an xs:string and copy the string representation to the new sequence;"
172- {temp .add (new StringValue (callingExpr , next .getStringValue ()));}
178+ // "For each item in S1, if the item is atomic, obtain the lexical representation of the item by
179+ // casting it to an xs:string and copy the string representation to the new sequence;"
180+ final StringValue stringRepresentation = new StringValue (callingExpr , next .getStringValue ());
181+ step2 .add (stringRepresentation );
173182 }
174183 }
175184
176185 context .pushDocumentContext ();
177186 try {
178187 final MemTreeBuilder builder = context .getDocumentBuilder ();
179188 final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver (callingExpr , builder , true );
180- for (final SequenceIterator i = temp .iterate (); i .hasNext (); ) {
189+ final String safeItemSeparator = itemSeparator == null ? "" : itemSeparator ;
190+ for (final SequenceIterator i = step2 .iterate (); i .hasNext (); ) {
181191 final Item next = i .nextItem ();
182192 if (Type .subTypeOf (next .getType (), Type .NODE )) {
183193 next .copyTo (context .getBroker (), receiver );
184194 } else {
185- receiver .characters (next .getStringValue ());
195+ final String stringValue = next .getStringValue ();
196+ receiver .characters (stringValue );
197+ }
198+ // add itemSeparator if there is a next item
199+ if (i .hasNext ()) {
200+ receiver .characters (safeItemSeparator );
186201 }
187202 }
188203 return (DocumentImpl )receiver .getDocument ();
0 commit comments