diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java index 02e81feb4..0d6411a67 100644 --- a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/cst/FunctionCallAccessor.java @@ -7,6 +7,7 @@ import gov.nist.secauto.metaschema.core.metapath.DynamicContext; import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.IFunction; import gov.nist.secauto.metaschema.core.metapath.function.library.ArrayGet; import gov.nist.secauto.metaschema.core.metapath.function.library.MapGet; import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue; @@ -84,7 +85,11 @@ public ISequence accept(DynamicContext dynamicContext, ISequenc .collect(Collectors.toUnmodifiableList())), dynamicContext, focus); - } + } else if (collection instanceof IFunction) { + return ((IFunction) collection).execute( ObjectUtils.notNull(getArguments().stream() + .map(expr -> expr.accept(dynamicContext, focus)) + .collect(Collectors.toUnmodifiableList())), dynamicContext, focus); + } // the value to find, which will be the key for a map or the index for an array IExpression argument = getArguments().stream().findFirst() 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 cc9997fcd..b14f2b658 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 @@ -91,6 +91,8 @@ public DefaultFunctionLibrary() { // NOPMD - intentional // P2: https://www.w3.org/TR/xpath-functions-31/#func-format-integer // P2: https://www.w3.org/TR/xpath-functions-31/#func-format-number // P2: https://www.w3.org/TR/xpath-functions-31/#func-format-time + // https://www.w3.org/TR/xpath-functions-31/#func-function-lookup + registerFunction(FnFunctionLookup.SIGNATURE); // P1: https://www.w3.org/TR/xpath-functions-31/#func-generate-id // https://www.w3.org/TR/xpath-functions-31/#func-has-children registerFunction(FnHasChildren.SIGNATURE_NO_ARG); diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookup.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookup.java new file mode 100644 index 000000000..b5083be3b --- /dev/null +++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookup.java @@ -0,0 +1,86 @@ +/* + * 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.StaticContext; +import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.FunctionLibrary; +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.IIntegerItem; +import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem; +import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; +import gov.nist.secauto.metaschema.core.metapath.type.IItemType; +import gov.nist.secauto.metaschema.core.qname.IEnhancedQName; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import java.util.List; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * /** Implements + * fn:function-lookup + * functions. + */ +public final class FnFunctionLookup { + @NonNull + private static final String NAME = "function-lookup"; + + @NonNull + static final IFunction SIGNATURE= IFunction.builder() + .name(NAME) + .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS) + .deterministic() + .contextIndependent() + .focusIndependent() + .argument(IArgument.builder() + .name("name") + .type(IStringItem.type()) + .one() + .build()) + .argument(IArgument.builder() + .name("arity") + .type(IIntegerItem.type()) + .one() + .build()) + .returnType(IItemType.function()) + .returnZeroOrOne() + .functionHandler(FnFunctionLookup::execute) + .build(); + + @SuppressWarnings("unused") + @NonNull + private static ISequence execute(@NonNull IFunction function, + @NonNull List> arguments, + @NonNull DynamicContext dynamicContext, + IItem focus) { + IStringItem name = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0).getFirstItem(true))); + IIntegerItem arity = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true))); + IFunction matchingFunction; + + try { + matchingFunction = dynamicContext.getStaticContext().lookupFunction(name.asString(), arity.asInteger().intValueExact()); + } catch (StaticMetapathException ex) { + if (ex.getCode() != StaticMetapathException.NO_FUNCTION_MATCH) { + throw ex; + } + matchingFunction = null; + } + + return ISequence.of(matchingFunction); + } + + private FnFunctionLookup() { + // disable construction + } +} diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookupTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookupTest.java new file mode 100644 index 000000000..d214aa49a --- /dev/null +++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnFunctionLookupTest.java @@ -0,0 +1,46 @@ +/* + * 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.string; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression; +import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression.ResultType; +import gov.nist.secauto.metaschema.core.metapath.MetapathException; +import gov.nist.secauto.metaschema.core.metapath.function.regex.RegularExpressionMetapathException; +import gov.nist.secauto.metaschema.core.metapath.item.IItem; +import gov.nist.secauto.metaschema.core.metapath.item.ISequence; +import gov.nist.secauto.metaschema.core.util.ObjectUtils; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.stream.Stream; + +import edu.umd.cs.findbugs.annotations.NonNull; + +class FnFunctionLookupTest + extends ExpressionTestBase { + + private static Stream provideValues() { // NOPMD - false positive + return Stream.of( + Arguments.of( + string("bcd"), + "function-lookup('substring', 2)('abcd', 2)")); + } + + @ParameterizedTest + @MethodSource("provideValues") + void test(@NonNull IItem expected, @NonNull String metapath) { + assertEquals(expected, IMetapathExpression.compile(metapath).evaluateAs(null, ResultType.ITEM, newDynamicContext())); + } +}