Skip to content

Commit

Permalink
Added support for the fn:namespace-uri Metapath function.
Browse files Browse the repository at this point in the history
  • Loading branch information
david-waltermire committed Dec 11, 2024
1 parent 204fa1e commit 0c0f023
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
// https://www.w3.org/TR/xpath-functions-31/#func-name
registerFunction(FnName.SIGNATURE_NO_ARG);
registerFunction(FnName.SIGNATURE_ONE_ARG);
// https://www.w3.org/TR/xpath-functions-31/#func-namespace-uri
registerFunction(FnNamespaceUri.SIGNATURE_NO_ARG);
registerFunction(FnNamespaceUri.SIGNATURE_ONE_ARG);
// https://www.w3.org/TR/xpath-functions-31/#func-node-name
// https://www.w3.org/TR/xpath-functions-31/#func-normalize-space
registerFunction(FnNormalizeSpace.SIGNATURE_NO_ARG);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

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.function.FunctionUtils;
import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
Expand Down Expand Up @@ -70,7 +69,7 @@ private static ISequence<IStringItem> executeNoArg(@NonNull IFunction function,
INodeItem.type().test(ObjectUtils.requireNonNull(focus)));

return ISequence.of(
IStringItem.valueOf(fnLocalName(arg, dynamicContext.getStaticContext())));
IStringItem.valueOf(fnLocalName(arg)));
}

@SuppressWarnings("unused")
Expand All @@ -82,7 +81,7 @@ private static ISequence<IStringItem> executeOneArg(@NonNull IFunction function,
INodeItem arg = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0)).getFirstItem(true));

return ISequence.of(
IStringItem.valueOf(arg == null ? "" : fnLocalName(arg, dynamicContext.getStaticContext())));
IStringItem.valueOf(arg == null ? "" : fnLocalName(arg)));
}

/**
Expand All @@ -94,12 +93,10 @@ private static ISequence<IStringItem> executeOneArg(@NonNull IFunction function,
*
* @param arg
* the node item to get the name for
* @param staticContext
* the static context used to resolve the namespace prefix
* @return the name of the node if it has one, or an empty string otherwise
*/
@NonNull
public static String fnLocalName(@NonNull INodeItem arg, @NonNull StaticContext staticContext) {
public static String fnLocalName(@NonNull INodeItem arg) {
return arg instanceof IDefinitionNodeItem
? ((IDefinitionNodeItem<?, ?>) arg).getQName().getLocalName()
: "";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.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.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.util.ObjectUtils;

import java.util.List;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
* /** Implements
* <a href= "https://www.w3.org/TR/xpath-functions-31/#func-name">fn:name</a>
* functions.
*/
public final class FnNamespaceUri {
@NonNull
private static final String NAME = "namespace-uri";
@NonNull
static final IFunction SIGNATURE_NO_ARG = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextDependent()
.focusDependent()
.returnType(IStringItem.type())
.returnOne()
.functionHandler(FnNamespaceUri::executeNoArg)
.build();
@NonNull
static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextIndependent()
.focusDependent()
.argument(IArgument.builder()
.name("arg")
.type(INodeItem.type())
.zeroOrOne()
.build())
.returnType(IStringItem.type())
.returnOne()
.functionHandler(FnNamespaceUri::executeOneArg)
.build();

@SuppressWarnings("unused")
@NonNull
private static ISequence<IStringItem> executeNoArg(@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {

INodeItem arg = FunctionUtils.asType(
// test that the focus is an INodeItem
INodeItem.type().test(ObjectUtils.requireNonNull(focus)));

return ISequence.of(
IStringItem.valueOf(fnNamespaceUri(arg)));
}

@SuppressWarnings("unused")
@NonNull
private static ISequence<IStringItem> executeOneArg(@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {
INodeItem arg = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0)).getFirstItem(true));

return ISequence.of(
IStringItem.valueOf(arg == null ? "" : fnNamespaceUri(arg)));
}

/**
* Get the namespace URI of the provided node item.
* <p>
* Based on the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-namespace-uri">fn:namespace-uri</a>
* function.
*
* @param arg
* the node item to get the namespace URI for
* @return the namespace URI of the node if it has one, or an empty string
* otherwise
*/
@NonNull
public static String fnNamespaceUri(@NonNull INodeItem arg) {
return arg instanceof IDefinitionNodeItem
? ((IDefinitionNodeItem<?, ?>) arg).getQName().getNamespace()
: "";
}

private FnNamespaceUri() {
// disable construction
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/

package gov.nist.secauto.metaschema.core.metapath.function.library;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.DynamicMetapathException;
import gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase;
import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression;
import gov.nist.secauto.metaschema.core.metapath.MetapathException;
import gov.nist.secauto.metaschema.core.metapath.function.library.impl.MockedDocumentGenerator;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException;
import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException;
import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;

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.stream.Stream;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

class FnNamespaceUriTest
extends ExpressionTestBase {

private static Stream<Arguments> provideValues() { // NOPMD - false positive
return Stream.of(
Arguments.of(
null,
"namespace-uri()"),
Arguments.of(
null,
"namespace-uri(.)"),
Arguments.of(
MockedDocumentGenerator.ROOT_QNAME,
"namespace-uri(/root)"),
Arguments.of(
MockedDocumentGenerator.ASSEMBLY_QNAME,
"namespace-uri(/root/assembly)"),
Arguments.of(
MockedDocumentGenerator.ASSEMBLY_FLAG_QNAME,
"namespace-uri(/root/assembly/@assembly-flag)"),
Arguments.of(
MockedDocumentGenerator.FIELD_QNAME,
"namespace-uri(/root/field)"),
Arguments.of(
MockedDocumentGenerator.FIELD_FLAG_QNAME,
"namespace-uri(/root/field/@field-flag)"));
}

@ParameterizedTest
@MethodSource("provideValues")
void test(@Nullable IEnhancedQName expected, @NonNull String metapath) {
DynamicContext dynamicContext = newDynamicContext();

IStringItem result = IMetapathExpression.compile(metapath, dynamicContext.getStaticContext())
.evaluateAs(
MockedDocumentGenerator.generateDocumentNodeItem(getContext()),
IMetapathExpression.ResultType.ITEM,
dynamicContext);
assertNotNull(result);
assertEquals(
expected == null
? ""
: expected.getNamespace(),
result.asString());
}

@Test
void testContextAbsent() {
DynamicContext dynamicContext = newDynamicContext();

MetapathException ex = assertThrows(MetapathException.class, () -> {
IMetapathExpression.compile("namespace-uri()", dynamicContext.getStaticContext())
.evaluateAs(null, IMetapathExpression.ResultType.ITEM, dynamicContext);
});
Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null;

assertAll(
() -> assertEquals(DynamicMetapathException.class, cause == null
? null
: cause.getClass()),
() -> assertEquals(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, cause instanceof DynamicMetapathException
? ((DynamicMetapathException) cause).getCode()
: null));
}

@Test
void testNotANode() {
DynamicContext dynamicContext = newDynamicContext();

MetapathException ex = assertThrows(MetapathException.class, () -> {
IMetapathExpression.compile("namespace-uri()", dynamicContext.getStaticContext())
.evaluateAs(IStringItem.valueOf("test"), IMetapathExpression.ResultType.ITEM, dynamicContext);
});
Throwable cause = ex.getCause() != null ? ex.getCause().getCause() : null;

assertAll(
() -> assertEquals(InvalidTypeMetapathException.class, cause == null
? null
: cause.getClass()),
() -> assertEquals(TypeMetapathException.INVALID_TYPE_ERROR, cause instanceof TypeMetapathException
? ((TypeMetapathException) cause).getCode()
: null));
}
}

0 comments on commit 0c0f023

Please sign in to comment.