Skip to content

Commit 4d13de3

Browse files
committed
[feature] Added API serialization for XDM Array types
1 parent a5a363c commit 4d13de3

File tree

5 files changed

+221
-77
lines changed

5 files changed

+221
-77
lines changed

exist-core/src/main/java/org/exist/storage/serializers/Serializer.java

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
import org.exist.xquery.Constants;
9696
import org.exist.xquery.XPathException;
9797
import org.exist.xquery.XQueryContext;
98+
import org.exist.xquery.functions.array.ArrayType;
9899
import org.exist.xquery.value.Item;
99100
import org.exist.xquery.value.NodeValue;
100101
import org.exist.xquery.value.Sequence;
@@ -173,6 +174,8 @@ public abstract class Serializer implements XMLReader {
173174
private static final QName ATTR_EXECUTION_TIME_QNAME = new QName("execution-time", Namespaces.EXIST_NS, "exist");
174175
private static final QName ATTR_TYPE_QNAME = new QName("type", Namespaces.EXIST_NS, "exist");
175176
private static final QName ELEM_VALUE_QNAME = new QName("value", Namespaces.EXIST_NS, "exist");
177+
private static final QName ELEM_ARRAY_QNAME = new QName("array", Namespaces.EXIST_NS, "exist");
178+
private static final QName ELEM_SEQUENCE_QNAME = new QName("sequence", Namespaces.EXIST_NS, "exist");
176179

177180
// required for XQJ/typed information implementation
178181
// -----------------------------------------
@@ -1205,19 +1208,56 @@ private void itemToSAX(final Item item, final boolean typed, final boolean wrap)
12051208
serializeToReceiver(node, false);
12061209
}
12071210
} else {
1211+
if (item.getType() == Type.ARRAY_ITEM) {
1212+
serializeTypeArray((ArrayType) item, typed, wrap);
1213+
} else {
1214+
serializeTypeAtomicValue(item, typed, wrap);
1215+
}
1216+
}
1217+
}
1218+
1219+
private void serializeTypeAtomicValue(final Item item, final boolean typed, final boolean wrap) throws SAXException {
1220+
if (typed) {
1221+
final AttrList attrs = new AttrList();
1222+
attrs.addAttribute(ATTR_TYPE_QNAME, Type.getTypeName(item.getType()));
1223+
receiver.startElement(ELEM_VALUE_QNAME, attrs);
1224+
}
1225+
1226+
try {
1227+
receiver.characters(item.getStringValue());
1228+
} catch (final XPathException e) {
1229+
throw new SAXException(e.getMessage(), e);
1230+
}
1231+
1232+
if (typed) {
1233+
receiver.endElement(ELEM_VALUE_QNAME);
1234+
}
1235+
}
1236+
1237+
private void serializeTypeArray(final ArrayType arrayType, final boolean typed, final boolean wrap) throws SAXException {
1238+
try {
12081239
if (typed) {
1209-
final AttrList attrs = new AttrList();
1210-
attrs.addAttribute(ATTR_TYPE_QNAME, Type.getTypeName(item.getType()));
1211-
receiver.startElement(ELEM_VALUE_QNAME, attrs);
1240+
receiver.startElement(ELEM_ARRAY_QNAME, null);
12121241
}
1213-
try {
1214-
receiver.characters(item.getStringValue());
1215-
} catch (final XPathException e) {
1216-
throw new SAXException(e.getMessage(), e);
1242+
1243+
for (final Sequence arrayItem : arrayType.toArray()) {
1244+
if (typed) {
1245+
receiver.startElement(ELEM_SEQUENCE_QNAME, null);
1246+
}
1247+
for (final SequenceIterator itItem = arrayItem.iterate(); itItem.hasNext(); ) {
1248+
final Item item = itItem.nextItem();
1249+
itemToSAX(item, typed, wrap);
1250+
}
1251+
if (typed) {
1252+
receiver.endElement(ELEM_SEQUENCE_QNAME);
1253+
}
12171254
}
1255+
12181256
if (typed) {
1219-
receiver.endElement(ELEM_VALUE_QNAME);
1257+
receiver.endElement(ELEM_ARRAY_QNAME);
12201258
}
1259+
} catch (final XPathException e) {
1260+
throw new SAXException(e.getMessage(), e);
12211261
}
12221262
}
12231263

exist-core/src/main/java/org/exist/xquery/XQueryContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ public Expression getRootExpression() {
835835
*
836836
* @return The next unique expression id.
837837
*/
838-
int nextExpressionId() {
838+
public int nextExpressionId() {
839839
return expressionCounter++;
840840
}
841841

exist-core/src/main/xsd/rest-api.xsd

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
<xs:element ref="exist:attribute"/>
9999
<xs:element ref="exist:document"/>
100100
<xs:element ref="exist:text"/>
101+
<xs:element ref="exist:array"/>
101102
</xs:choice>
102103
</xs:sequence>
103104
<xs:attributeGroup ref="exist:queryAttrs"></xs:attributeGroup>
@@ -113,6 +114,20 @@
113114
</xs:complexType>
114115
</xs:element>
115116

117+
<xs:element name="sequence">
118+
<xs:complexType>
119+
<xs:sequence>
120+
<xs:choice minOccurs="0" maxOccurs="unbounded">
121+
<xs:element ref="exist:value"/>
122+
<xs:element ref="exist:attribute"/>
123+
<xs:element ref="exist:document"/>
124+
<xs:element ref="exist:text"/>
125+
<xs:element ref="exist:array"/>
126+
</xs:choice>
127+
</xs:sequence>
128+
</xs:complexType>
129+
</xs:element>
130+
116131
<xs:element name="attribute">
117132
<xs:complexType>
118133
<xs:simpleContent>
@@ -137,6 +152,14 @@
137152

138153
<xs:element name="text" type="xs:string"/>
139154

155+
<xs:element name="array">
156+
<xs:complexType>
157+
<xs:sequence>
158+
<xs:element ref="exist:sequence" minOccurs="0" maxOccurs="unbounded"/>
159+
</xs:sequence>
160+
</xs:complexType>
161+
</xs:element>
162+
140163
<xs:element name="collection">
141164
<xs:complexType>
142165
<xs:sequence>

exist-core/src/test/java/org/exist/storage/serializers/NativeSerializerTest.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import org.exist.util.Configuration;
2626
import org.exist.util.serializer.SAXSerializer;
2727
import org.exist.xquery.XPathException;
28+
import org.exist.xquery.XQueryContext;
29+
import org.exist.xquery.functions.array.ArrayType;
2830
import org.exist.xquery.value.*;
2931
import org.junit.jupiter.params.ParameterizedTest;
3032
import org.junit.jupiter.params.provider.CsvSource;
@@ -37,6 +39,7 @@
3739
import java.util.ArrayList;
3840
import java.util.List;
3941

42+
import static com.evolvedbinary.j8fu.function.FunctionE.identity;
4043
import static org.easymock.EasyMock.*;
4144
import static org.exist.Namespaces.EXIST_NS;
4245
import static org.exist.Namespaces.EXIST_NS_PREFIX;
@@ -86,6 +89,25 @@ public void serializeText(final Wrapped wrapped, final Typed typed) throws SAXEx
8689
assertSerialize(sequence, wrapped == Wrapped.WRAPPED, typed == Typed.TYPED);
8790
}
8891

92+
@ParameterizedTest
93+
@CsvSource({"NOT_WRAPPED,NOT_TYPED", "WRAPPED,NOT_TYPED", "NOT_WRAPPED,TYPED", "WRAPPED,TYPED"})
94+
public void serializeArray(final Wrapped wrapped, final Typed typed) throws SAXException, XPathException, IOException {
95+
final XQueryContext mockContext = mock(XQueryContext.class);
96+
expect(mockContext.nextExpressionId()).andReturn(1).anyTimes();
97+
98+
replay(mockContext);
99+
100+
final Sequence sequence = new ValueSequence();
101+
final ArrayType array1 = new ArrayType(mockContext, List.of(ValueSequence.of(identity(), new StringValue("hello"), new IntegerValue(42)), ValueSequence.of(identity(), new StringValue("goodbye"))));
102+
sequence.add(array1);
103+
final ArrayType array2 = new ArrayType(mockContext, List.of(ValueSequence.of(identity(), new StringValue("in the beginning")), ValueSequence.of(identity(), new StringValue("but at the end"), new IntegerValue(42))));
104+
sequence.add(array2);
105+
106+
verify(mockContext);
107+
108+
assertSerialize(sequence, wrapped == Wrapped.WRAPPED, typed == Typed.TYPED);
109+
}
110+
89111
@ParameterizedTest
90112
@CsvSource({"NOT_WRAPPED,NOT_TYPED", "WRAPPED,NOT_TYPED", "NOT_WRAPPED,TYPED", "WRAPPED,TYPED"})
91113
public void serializeMixed(final Wrapped wrapped, final Typed typed) throws SAXException, XPathException, IOException {
@@ -144,7 +166,15 @@ private static List<String> toStrings(final Sequence sequence) throws XPathExcep
144166
final List<String> strings = new ArrayList<>(sequence.getItemCount());
145167
for (final SequenceIterator it = sequence.iterate(); it.hasNext();) {
146168
final Item item = it.nextItem();
147-
strings.add(item.getStringValue());
169+
if (item.getType() == Type.ARRAY_ITEM) {
170+
final StringBuilder arrayStringBuilder = new StringBuilder();
171+
for (final Sequence arrayItem : ((ArrayType) item).toArray()) {
172+
arrayStringBuilder.append(join(toStrings(arrayItem), ""));
173+
}
174+
strings.add(arrayStringBuilder.toString());
175+
} else {
176+
strings.add(item.getStringValue());
177+
}
148178
}
149179
return strings;
150180
}
@@ -166,6 +196,16 @@ private static List<String> type(final Sequence sequence, final boolean explicit
166196
final Item item = it.nextItem();
167197
if (item.getType() == Type.TEXT) {
168198
typed.add("<exist:text" + namespace + ">" + item.getStringValue() + "</exist:text>");
199+
} else if (item.getType() == Type.ARRAY_ITEM) {
200+
final StringBuilder builder = new StringBuilder();
201+
builder.append("<exist:array").append(namespace).append('>');
202+
for (final Sequence arrayItem : ((ArrayType) item).toArray()) {
203+
builder.append("<exist:sequence>");
204+
builder.append(join(type(arrayItem, explicitNamespace), ""));
205+
builder.append("</exist:sequence>");
206+
}
207+
builder.append("</exist:array>");
208+
typed.add(builder.toString());
169209
} else {
170210
typed.add("<exist:value" + namespace + " exist:type=\"" + Type.getTypeName(item.getType()) + "\">" + item.getStringValue() + "</exist:value>");
171211
}

0 commit comments

Comments
 (0)