Skip to content

Commit 7718b14

Browse files
committed
[hotfix] serialize item-separator can be empty, skip empty values
fixes #4704
1 parent bdd976d commit 7718b14

File tree

5 files changed

+84
-16
lines changed

5 files changed

+84
-16
lines changed

exist-core/src/main/java/org/exist/xquery/functions/fn/FunSerialize.java

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,16 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathExce
8282
try(final StringWriter writer = new StringWriter()) {
8383
final XQuerySerializer xqSerializer = new XQuerySerializer(context.getBroker(), outputProperties, writer);
8484

85-
Sequence seq = args[0];
85+
final Sequence input = args[0];
8686
if (xqSerializer.normalize()) {
8787
final String itemSeparator = outputProperties.getProperty(EXistOutputKeys.ITEM_SEPARATOR, DEFAULT_ITEM_SEPARATOR);
88-
seq = normalize(this, context, seq, itemSeparator);
88+
final Sequence normalized = normalize(this, context, input, itemSeparator);
89+
xqSerializer.serialize(normalized);
90+
}
91+
else {
92+
xqSerializer.serialize(input);
8993
}
9094

91-
xqSerializer.serialize(seq);
9295
return new StringValue(this, writer.toString());
9396
} catch (final IOException | SAXException e) {
9497
throw new XPathException(this, FnModule.SENR0001, e.getMessage());
@@ -97,9 +100,9 @@ public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathExce
97100

98101
public static Properties getSerializationProperties(final Expression callingExpr, final Item parametersItem) throws XPathException {
99102
final Properties outputProperties;
100-
if(parametersItem.getType() == Type.MAP) {
103+
if (parametersItem.getType() == Type.MAP) {
101104
outputProperties = SerializerUtils.getSerializationOptions(callingExpr, (AbstractMapType) parametersItem);
102-
} else if(isSerializationParametersElement(parametersItem)) {
105+
} else if (isSerializationParametersElement(parametersItem)) {
103106
outputProperties = new Properties();
104107
SerializerUtils.getSerializationOptions(callingExpr, (NodeValue) parametersItem, outputProperties);
105108
} else {
@@ -144,20 +147,33 @@ public static Sequence normalize(final Expression callingExpr, final XQueryConte
144147
if (input.isEmpty())
145148
// "If the sequence that is input to serialization is empty, create a sequence S1 that consists of a zero-length string."
146149
{return StringValue.EMPTY_STRING;}
147-
final String _separator = itemSeparator == null ? DEFAULT_ITEM_SEPARATOR : itemSeparator;
148150
final ValueSequence temp = new ValueSequence(input.getItemCount());
149151
for (final SequenceIterator i = input.iterate(); i.hasNext(); ) {
150152
final Item next = i.nextItem();
151-
if (Type.subTypeOf(next.getType(), Type.NODE)) {
152-
if (next.getType() == Type.ATTRIBUTE || next.getType() == Type.NAMESPACE || next.getType() == Type.FUNCTION_REFERENCE)
153-
{throw new XPathException(callingExpr, FnModule.SENR0001,
154-
"It is an error if an item in the sequence to serialize is an attribute node or a namespace node.");}
153+
final int itemType = next.getType();
154+
if (Type.subTypeOf(itemType, Type.NODE)) {
155+
if (itemType == Type.ATTRIBUTE || itemType == Type.NAMESPACE || itemType == Type.FUNCTION_REFERENCE) {
156+
throw new XPathException(callingExpr, FnModule.SENR0001,
157+
"It is an error if an item in the sequence to serialize is an attribute node or a namespace node.");
158+
}
159+
// if (itemType == Type.TEXT || itemType == Type.COMMENT) {
160+
// final StringValue stringRepresentation = new StringValue(callingExpr, next.getStringValue());
161+
// if (stringRepresentation.getStringValue().isEmpty()) {
162+
// continue;
163+
// }
164+
// temp.add(stringRepresentation);
165+
// }
155166
temp.add(next);
156167
} else {
157168
// atomic value
158169
// "For each item in S1, if the item is atomic, obtain the lexical representation of the item by
159170
// casting it to an xs:string and copy the string representation to the new sequence;"
160-
temp.add(new StringValue(callingExpr, next.getStringValue()));
171+
final StringValue stringRepresentation = new StringValue(callingExpr, next.getStringValue());
172+
// skip values that evaluate to an empty string
173+
if (stringRepresentation.getStringValue().isEmpty()) {
174+
continue;
175+
}
176+
temp.add(stringRepresentation);
161177
}
162178
}
163179

@@ -170,10 +186,12 @@ public static Sequence normalize(final Expression callingExpr, final XQueryConte
170186
if (Type.subTypeOf(next.getType(), Type.NODE)) {
171187
next.copyTo(context.getBroker(), receiver);
172188
} else {
173-
receiver.characters(next.getStringValue());
189+
final String stringValue = next.getStringValue();
190+
receiver.characters(stringValue);
174191
}
192+
// add itemSeparator if there is a next item and the current item is not an empty string
175193
if (i.hasNext()) {
176-
receiver.characters(_separator);
194+
receiver.characters(itemSeparator);
177195
}
178196
}
179197
return (DocumentImpl)receiver.getDocument();

exist-core/src/main/java/org/exist/xquery/functions/util/Eval.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ private Sequence doEval(final XQueryContext evalContext, final Sequence contextS
404404

405405
final Sequence seq;
406406
if (xqSerializer.normalize()) {
407-
seq = FunSerialize.normalize(this, context, result, null);
407+
seq = FunSerialize.normalize(this, context, result, "");
408408
} else {
409409
seq = result;
410410
}

exist-core/src/main/java/org/exist/xquery/util/SerializerUtils.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,12 @@ private static Sequence getParameterValue(final Expression parent, final Abstrac
450450
final Sequence providedParameterValue = entries.get(parameterConventionEntryKey);
451451

452452
// should we use the default value
453-
if (providedParameterValue == null || providedParameterValue.isEmpty() || (parameterConvention.getType() == Type.STRING && isEmptyStringValue(providedParameterValue))) {
453+
if (providedParameterValue == null || providedParameterValue.isEmpty() || (
454+
parameterConvention.getType() == Type.STRING && isEmptyStringValue(providedParameterValue) &&
455+
// allow empty separator #4704
456+
parameterConvention.getParameterName() != EXistOutputKeys.ITEM_SEPARATOR
457+
)
458+
) {
454459
// use default value
455460

456461
if (W3CParameterConvention.MEDIA_TYPE == parameterConvention) {

exist-core/src/test/java/xquery/xquery3/XQuery3Tests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
@XSuite.XSuiteFiles({
2929
"src/test/xquery/xquery3",
3030
"src/test/xquery/xquery3/transform",
31-
//Reminder - add an individual test like this - "src/test/xquery/xquery3/transform/<test-file>.xqm",
31+
// To add an individual test or only run a specific set of tests -
32+
// "src/test/xquery/xquery3/serialize.xql",
3233
})
3334
public class XQuery3Tests {
3435
}

exist-core/src/test/xquery/xquery3/serialize.xql

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,26 @@ function ser:serialize-atomic() {
224224
fn:serialize($nodes)
225225
};
226226

227+
(: test for https://github.com/eXist-db/exist/issues/4704 :)
228+
declare
229+
%test:assertEquals("aaabbb")
230+
function ser:serialize-atomic-empty-separator() {
231+
fn:serialize(("aaa", "bbb"), map {"item-separator": ""})
232+
};
233+
234+
(: test for https://github.com/eXist-db/exist/issues/4704 :)
235+
declare
236+
%test:assertEquals("aaabbb")
237+
function ser:serialize-atomic-empty-separator-xml-options() {
238+
fn:serialize(("aaa", "bbb"),
239+
<output:serialization-parameters
240+
xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
241+
<output:item-separator value=""/>
242+
</output:serialization-parameters>
243+
)
244+
};
245+
246+
227247
declare
228248
%test:assertEquals("")
229249
function ser:serialize-empty-sequence() {
@@ -935,3 +955,27 @@ function ser:sequence-of-nodes() {
935955
(<a>foo</a>, <b>bar</b>) => serialize()
936956
};
937957

958+
959+
declare
960+
%test:assertEquals("foo")
961+
function ser:skip-empty-no-separator() {
962+
(<a>foo</a>, <b></b>)/text() => serialize(map{"item-separator": "!"})
963+
};
964+
965+
declare
966+
%test:assertEquals("")
967+
function ser:empty-array-serializes-to-empty-string() {
968+
[] => serialize()
969+
};
970+
971+
declare
972+
%test:assertEquals("")
973+
function ser:array-with-members-serializes-to-empty-string() {
974+
["", ()] => serialize()
975+
};
976+
977+
declare
978+
%test:assertEquals("")
979+
function ser:sequence-of-empty-arrays-serializes-to-empty-string() {
980+
([],[]) => serialize()
981+
};

0 commit comments

Comments
 (0)