+ * Based on the XPath 3.1 fn:exactly-one
+ * function.
+ *
+ * @param sequence
+ * the sequence to evaluate
+ * @return the sequence if it has zero or one items
+ * @throws InvalidArgumentFunctionException
+ * with the code
+ * {@link InvalidArgumentFunctionException#INVALID_ARGUMENT_EXACTLY_ONE}
+ * if the sequence contains less or more than one item
+ */
+ @NonNull
+ public static ISequence> fnExactlyOne(@NonNull ISequence> sequence) {
+ if (sequence.size() != 1) {
+ throw new InvalidArgumentFunctionException(
+ InvalidArgumentFunctionException.INVALID_ARGUMENT_EXACTLY_ONE,
+ String.format("fn:exactly-one called with the sequence '%s' containing a number of items other than one.",
+ sequence.toSignature()));
+ }
+ return sequence;
+ }
+}
diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOneOrMore.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOneOrMore.java
new file mode 100644
index 000000000..0399ffa31
--- /dev/null
+++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnOneOrMore.java
@@ -0,0 +1,83 @@
+/*
+ * 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.IArgument;
+import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
+import gov.nist.secauto.metaschema.core.metapath.function.InvalidArgumentFunctionException;
+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 java.util.List;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Implements the XPath 3.1 fn:one-or-more
+ * function.
+ */
+public final class FnOneOrMore {
+ private static final String NAME = "one-or-more";
+ @NonNull
+ static final IFunction SIGNATURE = IFunction.builder()
+ .name(NAME)
+ .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
+ .deterministic()
+ .contextIndependent()
+ .focusIndependent()
+ .argument(IArgument.builder()
+ .name("arg")
+ .type(IItem.type())
+ .zeroOrMore()
+ .build())
+ .returnType(IItem.type())
+ .returnOneOrMore()
+ .functionHandler(FnOneOrMore::execute)
+ .build();
+
+ private FnOneOrMore() {
+ // disable construction
+ }
+
+ @SuppressWarnings("unused")
+ @NonNull
+ private static ISequence> execute(@NonNull IFunction function,
+ @NonNull List
+ * Based on the XPath 3.1 fn:one-or-more
+ * function.
+ *
+ * @param sequence
+ * the sequence to evaluate
+ * @return the sequence if it has zero or one items
+ * @throws InvalidArgumentFunctionException
+ * with the code
+ * {@link InvalidArgumentFunctionException#INVALID_ARGUMENT_ONE_OR_MORE}
+ * if the sequence contains no items
+ */
+ @NonNull
+ public static ISequence> fnOneOrMore(@NonNull ISequence> sequence) {
+ if (sequence.size() < 1) {
+ throw new InvalidArgumentFunctionException(
+ InvalidArgumentFunctionException.INVALID_ARGUMENT_ONE_OR_MORE,
+ String.format("fn:one-or-more called with the sequence '%s' containing less than one item.",
+ sequence.toSignature()));
+ }
+ return sequence;
+ }
+}
diff --git a/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnZeroOrOne.java b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnZeroOrOne.java
new file mode 100644
index 000000000..ed6dc3519
--- /dev/null
+++ b/core/src/main/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnZeroOrOne.java
@@ -0,0 +1,83 @@
+/*
+ * 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.IArgument;
+import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
+import gov.nist.secauto.metaschema.core.metapath.function.InvalidArgumentFunctionException;
+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 java.util.List;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Implements the XPath 3.1 fn:zero-or-one
+ * function.
+ */
+public final class FnZeroOrOne {
+ private static final String NAME = "zero-or-one";
+ @NonNull
+ static final IFunction SIGNATURE = IFunction.builder()
+ .name(NAME)
+ .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
+ .deterministic()
+ .contextIndependent()
+ .focusIndependent()
+ .argument(IArgument.builder()
+ .name("arg")
+ .type(IItem.type())
+ .zeroOrMore()
+ .build())
+ .returnType(IItem.type())
+ .returnZeroOrOne()
+ .functionHandler(FnZeroOrOne::execute)
+ .build();
+
+ private FnZeroOrOne() {
+ // disable construction
+ }
+
+ @SuppressWarnings("unused")
+ @NonNull
+ private static ISequence> execute(@NonNull IFunction function,
+ @NonNull List
+ * Based on the XPath 3.1 fn:zero-or-one
+ * function.
+ *
+ * @param sequence
+ * the sequence to evaluate
+ * @return the sequence if it has zero or one items
+ * @throws InvalidArgumentFunctionException
+ * with the code
+ * {@link InvalidArgumentFunctionException#INVALID_ARGUMENT_ZERO_OR_ONE}
+ * if the sequence contains more than one item
+ */
+ @NonNull
+ public static ISequence> fnZeroOrOne(@NonNull ISequence> sequence) {
+ if (sequence.size() > 1) {
+ throw new InvalidArgumentFunctionException(
+ InvalidArgumentFunctionException.INVALID_ARGUMENT_ZERO_OR_ONE,
+ String.format("fn:zero-or-one called with the sequence '%s' containing more than one item.",
+ sequence.toSignature()));
+ }
+ return sequence;
+ }
+}
diff --git a/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnExactlyOneTest.java b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnExactlyOneTest.java
new file mode 100644
index 000000000..8b85cb303
--- /dev/null
+++ b/core/src/test/java/gov/nist/secauto/metaschema/core/metapath/function/library/FnExactlyOneTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.integer;
+import static gov.nist.secauto.metaschema.core.metapath.TestUtils.sequence;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+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.InvalidArgumentFunctionException;
+import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
+
+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 FnExactlyOneTest
+ extends ExpressionTestBase {
+ private static Stream