Skip to content

Commit bc0813b

Browse files
authored
Merge pull request #4401 from evolvedbinary/feature/map-find#2
Implement map:find#2
2 parents 634e07f + 703e3b5 commit bc0813b

File tree

5 files changed

+223
-2
lines changed

5 files changed

+223
-2
lines changed

exist-core/src/main/java/org/exist/xquery/functions/map/MapFunction.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
import io.lacuna.bifurcan.IEntry;
2525
import org.exist.dom.QName;
2626
import org.exist.xquery.*;
27+
import org.exist.xquery.functions.array.ArrayType;
2728
import org.exist.xquery.value.*;
2829

2930
import javax.annotation.Nullable;
3031
import java.util.ArrayList;
32+
import java.util.Collections;
3133
import java.util.List;
3234

3335
import static org.exist.xquery.FunctionDSL.*;
@@ -46,11 +48,12 @@ public class MapFunction extends BasicFunction {
4648
private static final QName QN_KEYS = new QName("keys", MapModule.NAMESPACE_URI, MapModule.PREFIX);
4749
private static final QName QN_REMOVE = new QName("remove", MapModule.NAMESPACE_URI, MapModule.PREFIX);
4850
private static final QName QN_FOR_EACH = new QName("for-each", MapModule.NAMESPACE_URI, MapModule.PREFIX);
51+
private static final QName QN_FIND = new QName("find", MapModule.NAMESPACE_URI, MapModule.PREFIX);
4952

5053
private static final FunctionParameterSequenceType FS_PARAM_MAPS = optManyParam("maps", Type.MAP, "Existing maps to merge to create a new map.");
5154

5255
private static final String FS_MERGE_NAME = "merge";
53-
public final static FunctionSignature[] FS_MERGE = functionSignatures(
56+
public static final FunctionSignature[] FS_MERGE = functionSignatures(
5457
FS_MERGE_NAME,
5558
"Returns a map that combines the entries from a number of existing maps.",
5659
returns(Type.MAP, "A new map which is the result of merging the maps"),
@@ -65,6 +68,15 @@ public class MapFunction extends BasicFunction {
6568
)
6669
);
6770

71+
public static final FunctionSignature FS_FIND = functionSignature(
72+
QN_FIND,
73+
"Searches the supplied input sequence and any contained maps and arrays for a map entry with the supplied key, " +
74+
"and returns the corresponding values.",
75+
returns(Type.ARRAY, "An array containing the found values with the input key"),
76+
optManyParam("input", Type.ITEM, "The sequence of maps to search"),
77+
param("key", Type.ATOMIC, "The key to match")
78+
);
79+
6880
public final static FunctionSignature FNS_SIZE = new FunctionSignature(
6981
QN_SIZE,
7082
"Returns the number of entries in the supplied map.",
@@ -177,6 +189,8 @@ public Sequence eval(final Sequence[] args, final Sequence contextSequence) thro
177189
return remove(args);
178190
} else if (isCalledAs(QN_FOR_EACH.getLocalPart())) {
179191
return forEach(args);
192+
} else if (isCalledAs(QN_FIND.getLocalPart())) {
193+
return find(args);
180194
}
181195
throw new XPathException(this, "No function: " + getName() + "#" + getSignature().getArgumentCount());
182196
}
@@ -295,6 +309,57 @@ private Sequence forEach(final Sequence[] args) throws XPathException {
295309
}
296310
}
297311

312+
/**
313+
* Recursive helper for find
314+
*
315+
* Recursively find map members in a sequence
316+
* By searching each of the individual items in the sequence
317+
*
318+
* @param result add found values to this
319+
* @param key the key to match
320+
* @param sequence the sequence to search within
321+
*/
322+
private static void findRec(final ArrayType result, final AtomicValue key, final Sequence sequence) {
323+
for (int i = 0; i < sequence.getItemCount(); i++) {
324+
findRec(result, key, sequence.itemAt(i));
325+
}
326+
}
327+
328+
/**
329+
* Recursive helper for find
330+
*
331+
* Recursively find map members in items, which can only be maps or arrays
332+
* (They may be other types, but these are not containers)
333+
*
334+
* @param result add found values to this
335+
* @param key the key to match
336+
* @param item the item to search within
337+
*/
338+
private static void findRec(final ArrayType result, final AtomicValue key, final Item item) {
339+
if (Type.subTypeOf(item.getType(), Type.ARRAY)) {
340+
final ArrayType array = (ArrayType) item;
341+
for (final Sequence sequence : array.toArray()) {
342+
findRec(result, key, sequence);
343+
}
344+
} else if (Type.subTypeOf(item.getType(), Type.MAP)) {
345+
final AbstractMapType map = (AbstractMapType) item;
346+
//append the values in the map with the supplied key
347+
result.add(map.get(key));
348+
//recursively examine all the values in the map (key notwithstanding), they may in turn be maps
349+
for (final IEntry<AtomicValue, Sequence> entry : map) {
350+
MapFunction.findRec(result, key, entry.value());
351+
}
352+
}
353+
}
354+
355+
private ArrayType find(final Sequence[] args) {
356+
357+
final AtomicValue key = (AtomicValue) args[1].itemAt(0);
358+
final ArrayType result = new ArrayType(context, Collections.emptyList());
359+
MapFunction.findRec(result, key, args[0]);
360+
return result;
361+
}
362+
298363
private enum MergeDuplicates {
299364
REJECT,
300365
USE_FIRST,

exist-core/src/main/java/org/exist/xquery/functions/map/MapModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class MapModule extends AbstractInternalModule {
4545
private static final FunctionDef[] functions = {
4646
new FunctionDef(MapFunction.FS_MERGE[0], MapFunction.class),
4747
new FunctionDef(MapFunction.FS_MERGE[1], MapFunction.class),
48+
new FunctionDef(MapFunction.FS_FIND, MapFunction.class),
4849
new FunctionDef(MapFunction.FNS_SIZE, MapFunction.class),
4950
new FunctionDef(MapFunction.FNS_KEYS, MapFunction.class),
5051
new FunctionDef(MapFunction.FNS_CONTAINS, MapFunction.class),

exist-core/src/test/java/xquery/maps/MapTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
@RunWith(XSuite.class)
2828
@XSuite.XSuiteFiles({
29-
"src/test/xquery/maps"
29+
"src/test/xquery/maps"
3030
})
3131
public class MapTests {
3232
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
(:
2+
: eXist-db Open Source Native XML Database
3+
: Copyright (C) 2001 The eXist-db Authors
4+
:
5+
6+
: http://www.exist-db.org
7+
:
8+
: This library is free software; you can redistribute it and/or
9+
: modify it under the terms of the GNU Lesser General Public
10+
: License as published by the Free Software Foundation; either
11+
: version 2.1 of the License, or (at your option) any later version.
12+
:
13+
: This library is distributed in the hope that it will be useful,
14+
: but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
: Lesser General Public License for more details.
17+
:
18+
: You should have received a copy of the GNU Lesser General Public
19+
: License along with this library; if not, write to the Free Software
20+
: Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
:)
22+
xquery version "3.1";
23+
24+
module namespace mft="http://exist-db.org/xquery/test/maps";
25+
26+
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
27+
28+
declare
29+
%test:assertEmpty
30+
function mft:find-map-001() {
31+
let $s := ()
32+
return array:flatten(map:find($s, 17))
33+
};
34+
35+
declare
36+
%test:args(1)
37+
%test:assertEquals("Sunday", "Dimanche")
38+
%test:args(8)
39+
%test:assertEmpty
40+
function mft:find-map-004($param) {
41+
let $m1 := map{1:"Sunday",2:"Monday",3:"Tuesday",4:"Wednesday",5:"Thursday",6:"Friday",7:"Saturday"}
42+
let $m2 := map{2:"Lundi",3:"Mardi",4:"Mercredi",5:"Jeudi",6:"Vendredi",7:"Samedi",1:"Dimanche"}
43+
let $s := ($m1,$m2)
44+
return array:flatten(map:find($s, $param))
45+
};
46+
47+
declare
48+
%test:args(1)
49+
%test:assertEquals("Sunday", "Dimanche")
50+
%test:args(8)
51+
%test:assertEmpty
52+
function mft:find-map-004-all-values($param) {
53+
let $m1 := map{1:"Sunday",2:"Monday",3:"Tuesday",4:"Wednesday",5:"Thursday",6:"Friday",7:"Saturday"}
54+
let $m2 := map{2:"Lundi",3:"Mardi",4:"Mercredi",5:"Jeudi",6:"Vendredi",7:"Samedi",1:"Dimanche"}
55+
let $s := ($m1,$m2)
56+
return array:flatten(map:find($s, $param))
57+
};
58+
59+
declare
60+
%test:args(1)
61+
%test:assertEquals("Sunday", "Dimanche")
62+
%test:args(8)
63+
%test:assertEmpty
64+
function mft:find-map-006-ignore-non-collection-in-seq($param) {
65+
let $m1 := map{1:"Sunday",2:"Monday",3:"Tuesday",4:"Wednesday",5:"Thursday",6:"Friday",7:"Saturday"}
66+
let $m2 := map{2:"Lundi",3:"Mardi",4:"Mercredi",5:"Jeudi",6:"Vendredi",7:"Samedi",1:"Dimanche"}
67+
let $s := ($m1,$m2,1)
68+
return array:flatten(map:find($s, $param))
69+
};
70+
71+
declare
72+
%test:args(1)
73+
%test:assertEquals("Sunday", "Dimanche")
74+
%test:args(8)
75+
%test:assertEmpty
76+
function mft:find-map-006-ignore-non-collection-in-array($param) {
77+
let $m1 := map{1:"Sunday",2:"Monday",3:"Tuesday",4:"Wednesday",5:"Thursday",6:"Friday",7:"Saturday"}
78+
let $m2 := map{2:"Lundi",3:"Mardi",4:"Mercredi",5:"Jeudi",6:"Vendredi",7:"Samedi",1:"Dimanche"}
79+
let $s := [$m1,$m2,1]
80+
return array:flatten(map:find($s, $param))
81+
};
82+
83+
declare
84+
%test:args(1)
85+
%test:assertEquals("Sunday", "Dimanche")
86+
%test:args(8)
87+
%test:assertEmpty
88+
function mft:find-map-007-inner-maps($param) {
89+
let $m1 := map{1:"Sunday",2:"Monday",3:"Tuesday",4:"Wednesday",5:"Thursday",6:"Friday",7:"Saturday"}
90+
let $m2 := map{2:"Lundi",3:"Mardi",4:"Mercredi",5:"Jeudi",6:"Vendredi",7:"Samedi",1:"Dimanche"}
91+
let $m3 := map{"fr":$m2}
92+
let $s := [$m1,$m3]
93+
return array:flatten(map:find($s, $param))
94+
};
95+
96+
declare
97+
%test:args(6)
98+
%test:assertEmpty
99+
%test:args(7)
100+
%test:assertEquals("Saturday")
101+
function mft:find-map($param) {
102+
let $m := map { 7 : "Saturday" }
103+
return array:flatten(map:find($m, $param))
104+
};
105+
106+
declare
107+
%test:args(6)
108+
%test:assertEmpty
109+
%test:args(5)
110+
%test:assertEquals("Thursday")
111+
function mft:find-seq($param) {
112+
let $m1 := map { 7 : "Saturday" }
113+
let $m2 := map { 5 : "Thursday" }
114+
let $s := ($m1,$m2)
115+
return array:flatten(map:find($s, $param))
116+
};
117+
118+
declare variable $mft:inner-map := map { 5 : "Thursday", 6: "Friday" };
119+
120+
declare
121+
%test:args(3)
122+
%test:assertEmpty
123+
%test:args(5)
124+
%test:assertEquals("Thursday")
125+
%test:args(6)
126+
%test:assertEquals("Friday")
127+
function mft:find-inner($param) {
128+
let $s := [map { 7 : "Saturday" }, map { 4 : $mft:inner-map}]
129+
return array:flatten(map:find($s, $param))
130+
};
131+
132+
declare
133+
%test:args(3)
134+
%test:assertFalse
135+
%test:args(4)
136+
%test:assertTrue
137+
function mft:find-inner-map($param) {
138+
let $s := [map { 7 : "Saturday" }, map { 4 : $mft:inner-map}]
139+
return fn:deep-equal(array:flatten(map:find($s, $param)), $mft:inner-map)
140+
};
141+
142+
(: from the xquery spec :)
143+
declare
144+
%test:args(0)
145+
%test:assertEquals('no', 'non', 'nein')
146+
%test:args(1)
147+
%test:assertEquals('yes', 'oui', 'ja', 'doch')
148+
%test:args(2)
149+
%test:assertEmpty
150+
function mft:find-spec($param) {
151+
let $responses := [map{0:'no', 1:'yes'}, map{0:'non', 1:'oui'},
152+
map{0:'nein', 1:('ja', 'doch')}]
153+
return array:flatten(map:find($responses, $param))
154+
};
155+
File renamed without changes.

0 commit comments

Comments
 (0)