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 fn:substring to Metapath function library #142

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
94f8aaa
fn:string, add basic all param test for #132
aj-stein-gsa Sep 30, 2024
467cc80
fn:string, primitive fn impl and register it #132
aj-stein-gsa Sep 30, 2024
ecc8925
Feedback: add 2-arg and 3-arg function signatures
aj-stein-gsa Oct 6, 2024
d7018b1
Feedback, correct doc link from 3.0 to 3.1 spec
aj-stein-gsa Oct 6, 2024
3f33d9a
Feedback, make it spec-conformant, switch back to Decimal not Integer
aj-stein-gsa Oct 6, 2024
c21cefe
Feedback, align builder pattern args to properly require necessary fn…
aj-stein-gsa Oct 6, 2024
c2dc954
Implement 2 and 3-arg fn sig builders for #132
aj-stein-gsa Oct 6, 2024
a69acc7
Woops, execute correct typo in execute fn for #132
aj-stein-gsa Oct 6, 2024
b2ca01e
Match int->Decimal argument types for #132
aj-stein-gsa Oct 6, 2024
ba5002d
Return empty String sequence in kind for in #132
aj-stein-gsa Oct 6, 2024
d37a248
Rename fn substring-fnSubstring, fix tests for #6
aj-stein-gsa Oct 6, 2024
dbad189
Rename 3-arg execute fn appropriately for #6
aj-stein-gsa Oct 6, 2024
943cb1a
Remove pre-feedback arg type sig TODOs for #6
aj-stein-gsa Oct 6, 2024
3d71744
[WIP] Function and test cleanup for #6
aj-stein-gsa Oct 6, 2024
9eb4e78
[WIP] Array offsets are the devil learned in #132
aj-stein-gsa Oct 8, 2024
ccc86f4
Adjusted logic to produce passing junit tests. Commented out double-o…
david-waltermire Oct 8, 2024
a917c91
Correct comments to substring spec refs for #132
aj-stein-gsa Oct 8, 2024
8627871
Fixed Javadoc generation issue. Also bumped Javadoc plugin version.
david-waltermire Oct 8, 2024
a3bff5c
Merge pull request #2 from david-waltermire/feature/aj-stein-gsa/132-…
aj-stein-gsa Oct 8, 2024
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 @@ -155,10 +155,12 @@ public DefaultFunctionLibrary() { // NOPMD - intentional
// https://www.w3.org/TR/xpath-functions-31/#func-string
registerFunction(FnString.SIGNATURE_NO_ARG);
registerFunction(FnString.SIGNATURE_ONE_ARG);
// https://www.w3.org/TR/xpath-functions-30/#func-substring
registerFunction(FnSubstring.SIGNATURE_TWO_ARG);
registerFunction(FnSubstring.SIGNATURE_THREE_ARG);
aj-stein-gsa marked this conversation as resolved.
Show resolved Hide resolved
// P1: https://www.w3.org/TR/xpath-functions-31/#func-string-join
// P1: https://www.w3.org/TR/xpath-functions-31/#func-string-length
// P1: https://www.w3.org/TR/xpath-functions-31/#func-subsequence
// P1: https://www.w3.org/TR/xpath-functions-31/#func-substring
// P1: https://www.w3.org/TR/xpath-functions-31/#func-substring-after
// P1: https://www.w3.org/TR/xpath-functions-31/#func-substring-before
// https://www.w3.org/TR/xpath-functions-31/#func-sum
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* 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;

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

@NonNull
static final IFunction SIGNATURE_THREE_ARG = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.deterministic()
.contextIndependent()
.focusIndependent()
.argument(IArgument.builder()
.name("sourceString")
.type(IStringItem.class)
.zeroOrOne()
aj-stein-gsa marked this conversation as resolved.
Show resolved Hide resolved
.build())
.argument(IArgument.builder()
.name("start")
.type(IDecimalItem.class)
.one()
.build())
.argument(IArgument.builder()
.name("length")
.type(IDecimalItem.class)
.one()
.build())
.returnType(IStringItem.class)
.returnOne()
.functionHandler(FnSubstring::executeThreeArg)
.build();

private FnSubstring() {
// disable construction
}

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

IStringItem sourceString = FunctionUtils.asTypeOrNull(arguments.get(0).getFirstItem(true));

if (sourceString == null) {
return ISequence.of(IStringItem.valueOf(""));
}

IDecimalItem start = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true)));

int startIndex = start.round().asInteger().intValue();

return ISequence.of(IStringItem.valueOf(
fnSubstring(
sourceString.asString(),
startIndex,
sourceString.toString().length() - Math.max(startIndex, 1) + 1)));
}

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

IStringItem sourceString = FunctionUtils.asTypeOrNull(arguments.get(0).getFirstItem(true));

if (sourceString == null) {
return ISequence.of(IStringItem.valueOf(""));
}

IDecimalItem start = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true)));
IDecimalItem length = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(2).getFirstItem(true)));

return ISequence.of(IStringItem.valueOf(
fnSubstring(
sourceString.asString(),
start.round().asInteger().intValue(),
length.round().asInteger().intValue())));
}

/**
* An implementation of XPath 3.1 <a href=
* "https://www.w3.org/TR/xpath-functions-31/#func-substring">fn:substring</a>.
*
* @param source
* the source string to get a substring from
* @param start
* the 1-based start location
* @param length
* the substring length
* @return the substring
*/
@NonNull
public static String fnSubstring(
@NonNull String source,
int start,
int length) {
int sourceLength = source.length();

// XPath uses 1-based indexing, so subtract 1 for 0-based Java indexing
int startIndex = Math.max(start, 1);

// Handle negative or zero length
int endIndex = length <= 0 ? startIndex : Math.min(start + length, sourceLength + 1);

// Ensure startIndex is not greater than endIndex
startIndex = Math.min(startIndex, endIndex);

return ObjectUtils.notNull(source.substring(startIndex - 1, endIndex - 1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 FnSubstringTest
extends ExpressionTestBase {
private static Stream<Arguments> provideValues() { // NOPMD - false positive
return Stream.of(
Arguments.of(
string(" car"),
"substring('motor car', 6)"), // 6, 4
Arguments.of(
string("ada"),
"substring('metadata', 4, 3)"),
Arguments.of(
string("234"),
"substring('12345', 1.5, 2.6)"),
Arguments.of(
string("12"),
"substring('12345', 0, 3)"),
Arguments.of(
string(""),
"substring('12345', 5, -3)"),
Arguments.of(
string("1"),
"substring('12345', -3, 5)"),
Arguments.of(
string(""),
"substring((), 1, 3)")
// Arguments.of(
// string(""),
// "substring('12345', 0 div 0E0, 3)"),
// Arguments.of(
// string(""),
// "substring('12345', 1, 0 div 0E0)"),
// Arguments.of(
// string("12345"),
// "substring('12345', -42, 1 div 0E0)"),
// Arguments.of(
// string("12345"),
// "substring('12345', -1 div 0E0, 1 div 0E0)")
);
}

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

}
3 changes: 3 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
<plugin.antlr4test.version>1.22</plugin.antlr4test.version>
<plugin.appassembler.version>2.1.0</plugin.appassembler.version>
<plugin.git-commit-id.version>9.0.1</plugin.git-commit-id.version>
<plugin.maven-javadoc.version>3.10.1</plugin.maven-javadoc.version>
<plugin.maven-changes.version>2.12.1</plugin.maven-changes.version>
<plugin.maven-invoker.version>3.8.0</plugin.maven-invoker.version>
<plugin.maven-toolchains.version>3.2.0</plugin.maven-toolchains.version>
Expand Down Expand Up @@ -579,9 +580,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${plugin.maven-javadoc.version}</version>
<configuration>
<excludePackageNames>*.xmlbeans:*.xmlbeans.*:*.antlr</excludePackageNames>
<failOnWarnings>false</failOnWarnings>
<failOnError>true</failOnError>
</configuration>
</plugin>
<plugin>
Expand Down