From 55ee739480ecad4cde9fbeffee2b1c1159d76937 Mon Sep 17 00:00:00 2001 From: "A.J. Stein" Date: Tue, 3 Dec 2024 21:50:23 -0500 Subject: [PATCH] Completed the fn:index-of implementation, which has been adjusted to use the value comparison. (#207) Co-authored-by: David Waltermire --- .../library/DefaultFunctionLibrary.java | 3 +- .../metapath/function/library/FnIndexOf.java | 108 ++++++++++++++++++ .../function/library/FnIndexOfTest.java | 57 +++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java create mode 100644 core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOfTest.java diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java index 4c4e7b25f..419cb21aa 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/DefaultFunctionLibrary.java @@ -96,7 +96,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional // https://www.w3.org/TR/xpath-functions-31/#func-hours-from-time // https://www.w3.org/TR/xpath-functions-31/#func-implicit-timezone registerFunction(FnImplicitTimezone.SIGNATURE); - // P1: https://www.w3.org/TR/xpath-functions-31/#func-index-of + // https://www.w3.org/TR/xpath-functions-31/#func-index-of + registerFunction(FnIndexOf.SIGNATURE_TWO_ARG); // https://www.w3.org/TR/xpath-functions-31/#func-innermost // https://www.w3.org/TR/xpath-functions-31/#func-insert-before registerFunction(FnInsertBefore.SIGNATURE); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java new file mode 100644 index 000000000..71e176121 --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnIndexOf.java @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: none + * SPDX-License-Identifier: CC0-1.0 + */ + +package gov.nist.secauto.metaschema.core.metapath.function.library; + +import gov.nist.secauto.metaschema.core.metapath.DynamicContext; +import gov.nist.secauto.metaschema.core.metapath.MetapathConstants; +import gov.nist.secauto.metaschema.core.metapath.function.ComparisonFunctions; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils; +import gov.nist.secauto.metaschema.core.metapath.function.IArgument; +import gov.nist.secauto.metaschema.core.metapath.function.IFunction; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.ISequence; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IIntegerItem; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements fn:index-of + * functions. This implementation does not implement the three-arg variant with + * collation at this time. + */ +public final class FnIndexOf { + @NonNull + private static final String NAME = "index-of"; + @NonNull + static final IFunction SIGNATURE_TWO_ARG = IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextDependent() + .focusIndependent() + .argument(IArgument.builder() + .name("seq") + .type(IAnyAtomicItem.type()) + .zeroOrMore() + .build()) + .argument(IArgument.builder() + .name("search") + .type(IAnyAtomicItem.type()) + .one() + .build()) + .returnType(IIntegerItem.type()) + .returnZeroOrMore() + .functionHandler(FnIndexOf::executeTwoArg) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence executeTwoArg(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + ISequence 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())); + } +}