seq = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0)));
+ IAnyAtomicItem search = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true)));
+
+ if (seq.size() == 0) {
+ return ISequence.empty();
+ }
+ return fnIndexOf(seq, search);
+ }
+
+ /**
+ * Determine if the string provided in the first argument contains the string in
+ * the second argument as a substring.
+ *
+ * Based on the XPath 3.1 fn:index-of
+ * function.
+ *
+ * @param items
+ * the items to match against
+ * @param search
+ * the item to match
+ * @return a list of index numbers indicating the position of matches in the
+ * sequence
+ */
+ public static ISequence fnIndexOf(@NonNull List items,
+ @NonNull IAnyAtomicItem search) {
+ int index = 0;
+ ListIterator iterator = items.listIterator();
+ List indices = new ArrayList<>();
+ while (iterator.hasNext()) {
+ ++index;
+ IAnyAtomicItem item = iterator.next();
+ assert item != null;
+ // use the "eq" operator
+ if (ComparisonFunctions.valueCompairison(item, ComparisonFunctions.Operator.EQ, search).toBoolean()) {
+ // Offset for Metapath indices that start from 1
+ indices.add(IIntegerItem.valueOf(index));
+ }
+ }
+ return ISequence.ofCollection(indices);
+ }
+
+ private FnIndexOf() {
+ // disable construction
+ }
+}
diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOfTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOfTest.java
new file mode 100644
index 000000000..14544e77a
--- /dev/null
+++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOfTest.java
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: none
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+package gov.nist.secauto.metaschema.core.metapath.function.library;
+
+import static gov.nist.secauto.metaschema.core.metapath.TestUtils.integer;
+import static gov.nist.secauto.metaschema.core.metapath.TestUtils.sequence;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase;
+import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression;
+import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
+import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+class FnIndexOfTest
+ extends ExpressionTestBase {
+ private static Stream provideValues() { // NOPMD - false positive
+ return Stream.of(
+ Arguments.of(
+ sequence(),
+ "index-of((10, 20, 30, 40), 35)"),
+ Arguments.of(
+ sequence(integer(2), integer(5)),
+ "index-of((10, 20, 30, 30, 20, 10), 20)"),
+ Arguments.of(
+ sequence(integer(1), integer(4)),
+ "index-of(('a', 'sport', 'and', 'a', 'pasttime'), 'a')"),
+ // TODO: add current-date() test after metaschema-framework/metaschema-java#162
+ // complete
+ // Arguments.of(
+ // ISequence.empty(),
+ // "index-of(current-date(), 23)"),
+ Arguments.of(
+ sequence(integer(1)),
+ "index-of((true()), 'true')"),
+ Arguments.of(
+ sequence(integer(3), integer(4)),
+ "index-of([1, [5, 6], [6, 7]], 6)"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideValues")
+ void test(@NonNull ISequence expected, @NonNull String metapath) {
+ assertEquals(expected, IMetapathExpression.compile(metapath)
+ .evaluate(null, newDynamicContext()));
+ }
+}