Skip to content

Commit

Permalink
Completed support for the fn:local-name Metapath functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
david-waltermire committed Dec 11, 2024
1 parent 2bcf21d commit 204fa1e
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
registerFunction(FnInsertBefore.SIGNATURE);
// https://www.w3.org/TR/xpath-functions-31/#func-iri-to-uri
// P1: https://www.w3.org/TR/xpath-functions-31/#func-last
// https://www.w3.org/TR/xpath-functions-31/#func-local-name
registerFunction(FnLocalName.SIGNATURE_NO_ARG);
registerFunction(FnLocalName.SIGNATURE_ONE_ARG);
// https://www.w3.org/TR/xpath-functions-31/#func-lower-case
registerFunction(FnLowerCase.SIGNATURE_ONE_ARG);
// https://www.w3.org/TR/xpath-functions-31/#func-matches
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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.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 FnLocalName {
@NonNull
private static final String NAME = "local-name";
@NonNull
static final IFunction SIGNATURE_NO_ARG = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextDependent()
.focusDependent()
.returnType(IStringItem.type())
.returnOne()
.functionHandler(FnLocalName::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(FnLocalName::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(fnLocalName(arg, dynamicContext.getStaticContext())));
}

@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 ? "" : fnLocalName(arg, dynamicContext.getStaticContext())));
}

/**
* Get the name of the provided node item.
* <p>
* Based on the XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-local-name">fn:local-name</a>
* 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) {
return arg instanceof IDefinitionNodeItem
? ((IDefinitionNodeItem<?, ?>) arg).getQName().getLocalName()
: "";
}

private FnLocalName() {
// 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 FnLocalNameTest
extends ExpressionTestBase {

private static Stream<Arguments> provideValues() { // NOPMD - false positive
return Stream.of(
Arguments.of(
null,
"local-name()"),
Arguments.of(
null,
"local-name(.)"),
Arguments.of(
MockedDocumentGenerator.ROOT_QNAME,
"local-name(/root)"),
Arguments.of(
MockedDocumentGenerator.ASSEMBLY_QNAME,
"local-name(/root/assembly)"),
Arguments.of(
MockedDocumentGenerator.ASSEMBLY_FLAG_QNAME,
"local-name(/root/assembly/@assembly-flag)"),
Arguments.of(
MockedDocumentGenerator.FIELD_QNAME,
"local-name(/root/field)"),
Arguments.of(
MockedDocumentGenerator.FIELD_FLAG_QNAME,
"local-name(/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.getLocalName(),
result.asString());
}

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

MetapathException ex = assertThrows(MetapathException.class, () -> {
IMetapathExpression.compile("local-name()", 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("local-name()", 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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,75 +10,29 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import gov.nist.secauto.metaschema.core.mdm.IDMAssemblyNodeItem;
import gov.nist.secauto.metaschema.core.mdm.IDMDocumentNodeItem;
import gov.nist.secauto.metaschema.core.mdm.IDMFieldNodeItem;
import gov.nist.secauto.metaschema.core.mdm.IDMRootAssemblyNodeItem;
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.item.node.IDocumentNodeItem;
import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException;
import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException;
import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
import gov.nist.secauto.metaschema.core.model.IAssemblyInstance;
import gov.nist.secauto.metaschema.core.model.IFieldInstance;
import gov.nist.secauto.metaschema.core.model.IFlagInstance;
import gov.nist.secauto.metaschema.core.model.IResourceLocation;
import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
import gov.nist.secauto.metaschema.core.testing.MockedModelTestSupport;

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.net.URI;
import java.util.stream.Stream;

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

class FnNameTest
extends ExpressionTestBase {
@NonNull
private static final IEnhancedQName ROOT_QNAME = IEnhancedQName.of(NS, "root");
@NonNull
private static final IEnhancedQName ASSEMBLY_QNAME = IEnhancedQName.of(NS, "assembly");
@NonNull
private static final IEnhancedQName FIELD_QNAME = IEnhancedQName.of(NS, "field");
@NonNull
private static final IEnhancedQName ASSEMBLY_FLAG_QNAME = IEnhancedQName.of("assembly-flag");
@NonNull
private static final IEnhancedQName FIELD_FLAG_QNAME = IEnhancedQName.of("field-flag");

private IDocumentNodeItem newDocumentNodeItem() {
MockedModelTestSupport mocking = new MockedModelTestSupport(getContext());
IResourceLocation resourceLocation = mocking.mock(IResourceLocation.class);

IAssemblyDefinition rootDefinition = mocking.assembly().qname(ROOT_QNAME).rootQName(ROOT_QNAME).toDefinition();
IAssemblyInstance assemblyInstance = mocking.assembly().qname(ASSEMBLY_QNAME).toInstance(rootDefinition);
IFlagInstance assemblyFlag = mocking.flag().qname(ASSEMBLY_FLAG_QNAME).toInstance(assemblyInstance.getDefinition());
IFieldInstance fieldInstance = mocking.field().qname(FIELD_QNAME).toInstance(rootDefinition);
IFlagInstance fieldFlag = mocking.flag().qname(FIELD_FLAG_QNAME).toInstance(fieldInstance.getDefinition());

IDMDocumentNodeItem document = IDMDocumentNodeItem.newInstance(
URI.create("https://example.com/resource"),
resourceLocation,
rootDefinition,
resourceLocation);
IDMRootAssemblyNodeItem root = document.getRootAssemblyNodeItem();
IDMAssemblyNodeItem assembly = root.newAssembly(assemblyInstance, resourceLocation);
assembly.newFlag(assemblyFlag, resourceLocation, IStringItem.valueOf("assembly-flag"));
IDMFieldNodeItem field = root.newField(fieldInstance, resourceLocation, IStringItem.valueOf("field"));
field.newFlag(fieldFlag, resourceLocation, IStringItem.valueOf("field-flag"));

return document;
}

private static Stream<Arguments> provideValues() { // NOPMD - false positive
return Stream.of(
Arguments.of(
Expand All @@ -88,19 +42,19 @@ private static Stream<Arguments> provideValues() { // NOPMD - false positive
null,
"name(.)"),
Arguments.of(
ROOT_QNAME,
MockedDocumentGenerator.ROOT_QNAME,
"name(/root)"),
Arguments.of(
ASSEMBLY_QNAME,
MockedDocumentGenerator.ASSEMBLY_QNAME,
"name(/root/assembly)"),
Arguments.of(
ASSEMBLY_FLAG_QNAME,
MockedDocumentGenerator.ASSEMBLY_FLAG_QNAME,
"name(/root/assembly/@assembly-flag)"),
Arguments.of(
FIELD_QNAME,
MockedDocumentGenerator.FIELD_QNAME,
"name(/root/field)"),
Arguments.of(
FIELD_FLAG_QNAME,
MockedDocumentGenerator.FIELD_FLAG_QNAME,
"name(/root/field/@field-flag)"));
}

Expand All @@ -110,7 +64,10 @@ void test(@Nullable IEnhancedQName expected, @NonNull String metapath) {
DynamicContext dynamicContext = newDynamicContext();

IStringItem result = IMetapathExpression.compile(metapath, dynamicContext.getStaticContext())
.evaluateAs(newDocumentNodeItem(), IMetapathExpression.ResultType.ITEM, dynamicContext);
.evaluateAs(
MockedDocumentGenerator.generateDocumentNodeItem(getContext()),
IMetapathExpression.ResultType.ITEM,
dynamicContext);
assertNotNull(result);
assertEquals(
expected == null
Expand Down
Loading

0 comments on commit 204fa1e

Please sign in to comment.