2020 */
2121package org .exist .storage .serializers ;
2222
23+ import com .evolvedbinary .j8fu .tuple .Tuple2 ;
24+ import io .lacuna .bifurcan .IEntry ;
2325import org .easymock .Capture ;
2426import org .exist .dom .memtree .MemTreeBuilder ;
2527import org .exist .util .Configuration ;
2628import org .exist .util .serializer .SAXSerializer ;
2729import org .exist .xquery .XPathException ;
2830import org .exist .xquery .XQueryContext ;
2931import org .exist .xquery .functions .array .ArrayType ;
32+ import org .exist .xquery .functions .map .MapType ;
3033import org .exist .xquery .value .*;
3134import org .junit .jupiter .params .ParameterizedTest ;
3235import org .junit .jupiter .params .provider .CsvSource ;
4043import java .util .List ;
4144
4245import static com .evolvedbinary .j8fu .function .FunctionE .identity ;
46+ import static com .evolvedbinary .j8fu .tuple .Tuple .Tuple ;
4347import static org .easymock .EasyMock .*;
4448import static org .exist .Namespaces .EXIST_NS ;
4549import static org .exist .Namespaces .EXIST_NS_PREFIX ;
@@ -108,6 +112,25 @@ public void serializeArray(final Wrapped wrapped, final Typed typed) throws SAXE
108112 assertSerialize (sequence , wrapped == Wrapped .WRAPPED , typed == Typed .TYPED );
109113 }
110114
115+ @ ParameterizedTest
116+ @ CsvSource ({"NOT_WRAPPED,NOT_TYPED" , "WRAPPED,NOT_TYPED" , "NOT_WRAPPED,TYPED" , "WRAPPED,TYPED" })
117+ public void serializeMap (final Wrapped wrapped , final Typed typed ) throws SAXException , XPathException , IOException {
118+ final XQueryContext mockContext = mock (XQueryContext .class );
119+ expect (mockContext .nextExpressionId ()).andReturn (1 ).anyTimes ();
120+
121+ replay (mockContext );
122+
123+ final Sequence sequence = new ValueSequence ();
124+ final MapType map1 = new MapType (mockContext , null , List .of (Tuple (new StringValue ("key1" ), ValueSequence .of (identity (), new StringValue ("hello" ), new IntegerValue (42 ))), Tuple (new StringValue ("key2" ), ValueSequence .of (identity (), new StringValue ("goodbye" )))));
125+ sequence .add (map1 );
126+ final MapType map2 = new MapType (mockContext , null , List .of (Tuple (new StringValue ("key3" ), ValueSequence .of (identity (), new StringValue ("in the beginning" ), new StringValue ("but at the end" ), new IntegerValue (42 )))));
127+ sequence .add (map2 );
128+
129+ verify (mockContext );
130+
131+ assertSerialize (sequence , wrapped == Wrapped .WRAPPED , typed == Typed .TYPED );
132+ }
133+
111134 @ ParameterizedTest
112135 @ CsvSource ({"NOT_WRAPPED,NOT_TYPED" , "WRAPPED,NOT_TYPED" , "NOT_WRAPPED,TYPED" , "WRAPPED,TYPED" })
113136 public void serializeMixed (final Wrapped wrapped , final Typed typed ) throws SAXException , XPathException , IOException {
@@ -172,7 +195,16 @@ private static List<String> toStrings(final Sequence sequence) throws XPathExcep
172195 arrayStringBuilder .append (join (toStrings (arrayItem ), "" ));
173196 }
174197 strings .add (arrayStringBuilder .toString ());
175- } else {
198+
199+ } else if (item .getType () == Type .MAP_ITEM ) {
200+ final StringBuilder mapStringBuilder = new StringBuilder ();
201+ for (final IEntry <AtomicValue , Sequence > mapEntry : (MapType ) item ) {
202+ mapStringBuilder .append (mapEntry .key ().getStringValue ());
203+ mapStringBuilder .append (join (toStrings (mapEntry .value ()), "" ));
204+ }
205+ strings .add (mapStringBuilder .toString ());
206+
207+ } else {
176208 strings .add (item .getStringValue ());
177209 }
178210 }
@@ -194,8 +226,10 @@ private static List<String> type(final Sequence sequence, final boolean explicit
194226 final List <String > typed = new ArrayList <>(sequence .getItemCount ());
195227 for (final SequenceIterator it = sequence .iterate (); it .hasNext ();) {
196228 final Item item = it .nextItem ();
229+
197230 if (item .getType () == Type .TEXT ) {
198231 typed .add ("<exist:text" + namespace + ">" + item .getStringValue () + "</exist:text>" );
232+
199233 } else if (item .getType () == Type .ARRAY_ITEM ) {
200234 final StringBuilder builder = new StringBuilder ();
201235 builder .append ("<exist:array" ).append (namespace ).append ('>' );
@@ -206,13 +240,34 @@ private static List<String> type(final Sequence sequence, final boolean explicit
206240 }
207241 builder .append ("</exist:array>" );
208242 typed .add (builder .toString ());
243+
244+ } else if (item .getType () == Type .MAP_ITEM ) {
245+ final StringBuilder builder = new StringBuilder ();
246+ builder .append ("<exist:map" ).append (namespace ).append ('>' );
247+ for (final IEntry <AtomicValue , Sequence > mapEntry : (MapType ) item ) {
248+ builder .append ("<exist:entry>" );
249+ builder .append ("<exist:key>" );
250+ builder .append (atomicValue (namespace , mapEntry .key ()));
251+ builder .append ("</exist:key>" );
252+ builder .append ("<exist:sequence>" );
253+ builder .append (join (type (mapEntry .value (), explicitNamespace ), "" ));
254+ builder .append ("</exist:sequence>" );
255+ builder .append ("</exist:entry>" );
256+ }
257+ builder .append ("</exist:map>" );
258+ typed .add (builder .toString ());
259+
209260 } else {
210- typed .add ("<exist:value" + namespace + " exist:type= \" " + Type . getTypeName ( item . getType ()) + " \" >" + item . getStringValue () + "</exist:value>" );
261+ typed .add (atomicValue ( namespace , item ) );
211262 }
212263 }
213264 return typed ;
214265 }
215266
267+ private static String atomicValue (final String namespace , final Item item ) throws XPathException {
268+ return "<exist:value" + namespace + " exist:type=\" " + Type .getTypeName (item .getType ()) + "\" >" + item .getStringValue () + "</exist:value>" ;
269+ }
270+
216271 private static String wrap (final List <String > items ) {
217272 final StringBuilder builder = new StringBuilder ()
218273 .append ("<exist:result xmlns:exist=\" http://exist.sourceforge.net/NS/exist\" exist:hits=\" " )
0 commit comments