Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add substring-before() implementation #194

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
// P1: https://www.w3.org/TR/xpath-functions-31/#func-subsequence
// P1: https://www.w3.org/TR/xpath-functions-31/#func-substring-after
// P1: https://www.w3.org/TR/xpath-functions-31/#func-substring-before
registerFunction(FnSubstringBefore.SIGNATURE_TWO_ARG);
// https://www.w3.org/TR/xpath-functions-31/#func-sum
registerFunction(FnSum.SIGNATURE_ONE_ARG);
registerFunction(FnSum.SIGNATURE_TWO_ARG);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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.ISequence;
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.atomic.IDecimalItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.List;

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

import org.apache.commons.lang3.StringUtils;

/**
* Implements <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-substring-before">fn:substring-before</a>.
*/
public final class FnSubstringBefore {
private static final String NAME = "substring-before";
@NonNull
static final IFunction SIGNATURE_TWO_ARG = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextIndependent()
.focusIndependent()
.argument(IArgument.builder()
.name("arg1")
.type(IStringItem.class)
.zeroOrOne()
.build())
.argument(IArgument.builder()
.name("arg2")
.type(IStringItem.class)
.zeroOrOne()
.build())
.returnType(IStringItem.class)
.returnOne()
.functionHandler(FnSubstringBefore::executeTwoArg)
.build();

private FnSubstringBefore() {
// disable construction
}

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

// From the XPath 3.1 specification:
// If the value of $arg1 or $arg2 is the empty sequence, or contains only
// ignorable collation units, it is interpreted as the zero-length string.
IStringItem arg1 = arguments.get(0).isEmpty() ? IStringItem.valueOf("") : FunctionUtils.asTypeOrNull(arguments.get(0).getFirstItem(true));
IStringItem arg2 = arguments.get(1).isEmpty() ? IStringItem.valueOf("") : FunctionUtils.asTypeOrNull(arguments.get(1).getFirstItem(true));

return ISequence.of(IStringItem.valueOf(fnSubstringBefore(arg1.asString(), arg2.asString())));
}

/**
* An implementation of XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-substring-before">fn:substring-before</a>.
*
* @param arg1
* the source string to get a substring from
* @param arg2
* the substring to match and find the substring to return before the match
* @return the substring
*/
@NonNull
public static String fnSubstringBefore(
@NonNull String arg1,
@NonNull String arg2) {
return StringUtils.substringBefore(arg1,arg2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 gov.nist.secauto.metaschema.core.metapath.ExpressionTestBase;
import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;

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 FnSubstringBeforeTest
extends ExpressionTestBase {
private static Stream<Arguments> provideValues() { // NOPMD - false positive
return Stream.of(
Arguments.of(
string("t"),
"substring-before('tattoo', 'attoo')"),
Arguments.of(
string(""),
"substring-before('tattoo', 'tatto')"),
Arguments.of(
string(""),
"substring-before((), ())")
);
}

@ParameterizedTest
@MethodSource("provideValues")
void testExpression(@NonNull IStringItem expected, @NonNull String metapath) {
assertEquals(
expected,
MetapathExpression.compile(metapath)
.evaluateAs(null, MetapathExpression.ResultType.ITEM, newDynamicContext()));
}

}