Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1f3c10e
[feature] Create the function stub and add to the fn namespace
marmoure Sep 7, 2026
f8481bd
[feature] Add error codes.
marmoure Sep 8, 2026
df6b12c
[bugfix] Typo
marmoure Sep 9, 2026
1390a7c
[feature] Use markup blitz for the IXML grammar parsing
marmoure Sep 9, 2026
8585ef0
[feature] First attempt at creating a User defined function and retur…
marmoure Sep 9, 2026
6cb1da6
[feature] The inner function can take input now, and it retuns it as …
marmoure Sep 9, 2026
e72d75c
[feature] Use the default IXML parser to parse input and return it a…
marmoure Sep 9, 2026
1be12bb
[feature] Build a document from the Blitz grammar response.
marmoure Sep 10, 2026
537a8bd
Review and discussion
adamretter Sep 10, 2026
9fb2203
[WIP] Trying to resolve args and get function call running
marmoure Sep 11, 2026
460dec7
[wip] The 3 paths are working
marmoure Sep 14, 2026
9fde446
[feature] Set the fail on error option
marmoure Sep 14, 2026
e104b6a
[feature] Use MapType from the xquery functions instead
marmoure Sep 14, 2026
e6891e4
[feature] Add error codes
marmoure Sep 14, 2026
bb44afe
[bugfix] Update the inner function description.
marmoure Sep 15, 2026
5d6eaa3
[test] Create unit test for the invisible-xml function
marmoure Sep 15, 2026
96ced97
[bugfix] Make sure the param is registered
marmoure Sep 16, 2026
cd1f623
[bugfix] clean up
marmoure Sep 17, 2026
dd6e448
[feature] Remove the deprecated error codes and use new w3c error code
marmoure Sep 22, 2026
5c4f914
[refactor] Remove xquery test case comment from unit tests file
marmoure Sep 22, 2026
7d9ae4a
[bugfix] Static variables should be upper-case
marmoure Sep 22, 2026
9bdc7ac
[bugfix] Refrence the actual error code instead of reproducing it as …
marmoure Sep 22, 2026
39bfd54
[test] test case when no grammar was provided
marmoure Sep 22, 2026
aa29c5f
[bugfix] Fix the license headers
marmoure Sep 22, 2026
17c83e9
[feature] Only the grammar can be null, left and right values can nev…
marmoure Sep 22, 2026
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
10 changes: 10 additions & 0 deletions exist-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,12 @@
<!-- scope>test</scope --> <!-- not just test scope, as needed for org.exist.test -->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to update the license plugin file details in exist-core/pom.xml please

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in bed1ba4

</dependency>

<dependency>
<groupId>de.bottlecaps</groupId>
<artifactId>markup-blitz</artifactId>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which version of Java does this require? Will it be compatible with Elemental 6.x.x (JDK 8)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java 17, from the Blitz readme on github "Use JDK 17 or higher. For building Markup Blitz"
https://github.com/GuntherRademacher/markup-blitz

<version>1.12</version>
</dependency>

<!-- test dependencies -->
<dependency>
<groupId>xyz.elemental.fork.org.exist-db</groupId>
Expand Down Expand Up @@ -793,6 +799,8 @@
<include>src/test/java/org/exist/xquery/functions/xquery3/SerializeTest.java</include>
<include>src/main/java/org/exist/xquery/value/ArrayWrapper.java</include>
<include>src/test/java/org/exist/xquery/value/DateTimeTypesTest.java</include>
<include>src/main/java/org/exist/xquery/functions/fn/FnInvisibleXml.java</include>
<include>src/test/java/org/exist/xquery/functions/fn/FnInvisibleXmlTest.java</include>
</includes>
</licenseSet>

Expand Down Expand Up @@ -2339,6 +2347,8 @@
<exclude>src/main/java/org/exist/xquery/functions/fn/FnFormatDates.java</exclude>
<exclude>src/main/java/org/exist/xquery/functions/fn/FnHasChildren.java</exclude>
<exclude>src/main/java/org/exist/xquery/functions/fn/FnInnerMost.java</exclude>
<exclude>src/main/java/org/exist/xquery/functions/fn/FnInvisibleXml.java</exclude>
<exclude>src/test/java/org/exist/xquery/functions/fn/FnInvisibleXmlTest.java</exclude>
<exclude>src/main/java/org/exist/xquery/functions/fn/FnModule.java</exclude>
<exclude>src/main/java/org/exist/xquery/functions/fn/FnOuterMost.java</exclude>
<exclude>src/main/java/org/exist/xquery/functions/fn/FunAbs.java</exclude>
Expand Down
3 changes: 3 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/ErrorCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ public enum W3CErrorCode implements IErrorCode {
FOXT0003 ("XSLT transformation failed"),
FOXT0004 ("XSLT transformation has been disabled"),
FOXT0006 ("XSLT output contains non-accepted characters"),
FOIX0001 ("Invalid Invisible XML grammar."),
FOIX0002 ("Input provided could not be parsed successfully."),
FOIX0003 ("No Invisible XML processor is available."),
XTSE0165 ("It is a static error if the processor is not able to retrieve the resource identified by the URI reference [ in the href attribute of xsl:include or xsl:import] , or if the resource that is retrieved does not contain a stylesheet module conforming to this specification.");

private final ErrorCode errorCode;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Elemental
* Copyright (C) 2024, Evolved Binary Ltd
*
* admin@evolvedbinary.com
* https://www.evolvedbinary.com | https://www.elemental.xyz
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; version 2.1.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package org.exist.xquery.functions.fn;

import static com.evolvedbinary.j8fu.Either.Left;
import static com.evolvedbinary.j8fu.Either.Right;
import static org.exist.xquery.FunctionDSL.optParam;
import static org.exist.xquery.functions.fn.FnModule.functionSignature;
import static org.exist.xquery.FunctionDSL.param;
import static org.exist.xquery.FunctionDSL.returns;

import com.evolvedbinary.j8fu.Either;
import org.exist.Namespaces;
import org.apache.commons.io.output.StringBuilderWriter;
import org.exist.dom.memtree.SAXAdapter;
import org.exist.util.XMLReaderPool;
import org.exist.util.serializer.XQuerySerializer;
import org.exist.xquery.*;
import org.exist.xquery.functions.map.MapType;
import org.exist.xquery.value.*;
import org.w3c.dom.Element;
import org.xml.sax.*;

import de.bottlecaps.markup.Blitz;
import de.bottlecaps.markup.BlitzException;
import de.bottlecaps.markup.blitz.Parser;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.StringReader;
import java.util.Properties;

public class FnInvisibleXml extends BasicFunction {

private static final String FS_INVISIBLE_XML_NAME = "invisible-xml";

final static FunctionSignature FS_INVISIBLE_XML = functionSignature(
FS_INVISIBLE_XML_NAME,
"Evaluates invisible XML.",
returns(Type.FUNCTION, "The iXML parsing function"),
optParam("grammar", Type.ITEM, "The iXML grammar"),
optParam("options", Type.MAP_ITEM, "Options for the iXML parser"));

public FnInvisibleXml(final XQueryContext context, final FunctionSignature signature) {
super(context, signature);
}

@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
final Sequence optionsArg = args[1];
final MapType options = optionsArg.isEmpty()
? new MapType(context)
: (MapType) optionsArg.itemAt(0);

final IxmlParserFunction fn;
final Sequence grammarArg = args[0];
if (grammarArg.isEmpty()) {
// no grammar provided
fn = new IxmlParserFunction(context, options);

} else if (grammarArg.getItemType() == Type.STRING) {
// grammar is a string
final StringValue grammarString = grammarArg.itemAt(0).toJavaObject(StringValue.class);
fn = new IxmlParserFunction(context, grammarString, options);

} else {
// grammar is an element
final Element grammarItem = grammarArg.itemAt(0).toJavaObject(Element.class);
fn = new IxmlParserFunction(context, grammarItem, options);
}

final FunctionCall invisibleXmlFunctionCall = new FunctionCall(context, fn);
return new FunctionReference(invisibleXmlFunctionCall);
}

private static class IxmlParserFunction extends UserDefinedFunction {

private static final String FS_PARSE_INVISIBLE_XML_NAME = "parse-invisible-xml";
private static final FunctionSignature FS_PARSE_INVISIBLE_XML = functionSignature(
FS_PARSE_INVISIBLE_XML_NAME,
"Parses the input using the given iXML grammar.",
returns(Type.DOCUMENT, "The parsed document"),
param("input", Type.STRING, "The input to parse"));

private static final StringValue FAIL_ON_ERROR_KEY = new StringValue("fail-on-error");

@Nullable
final Either<StringValue, Element> grammar;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, grammar can not be null, only its left value could be null.

What we really have in the code at the moment is:

final Either<@Nullable StringValue, Element> grammar;

I think it might be better to change the code so that grammar can be null, and the left or right value is never null please.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b6dbf1c

final MapType options;

IxmlParserFunction(final XQueryContext context, final MapType options) throws XPathException {
super(context, FS_PARSE_INVISIBLE_XML);
addVariable("input");
this.grammar = null;
this.options = options;
}

IxmlParserFunction(final XQueryContext context, final StringValue grammar,
final MapType options) throws XPathException {
super(context, FS_PARSE_INVISIBLE_XML);
addVariable("input");
this.grammar = Left(grammar);
this.options = options;
}

IxmlParserFunction(final XQueryContext context, final Element grammar,
final MapType options) throws XPathException {
super(context, FS_PARSE_INVISIBLE_XML);
addVariable("input");
this.grammar = Right(grammar);
this.options = options;
}

@Override
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {

// get the input
final Sequence inputArg = getCurrentArguments()[0];
final String input = inputArg.getStringValue();

final String ixmlGrammar;
if (grammar == null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that grammar can be null?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b6dbf1c

// no grammar provided: use the default Invisible XML specification grammar
try {
ixmlGrammar = Blitz.ixmlGrammar();
} catch (final BlitzException e) {
throw new XPathException(this, ErrorCodes.W3CErrorCode.FOIX0003.getErrorCode(), e.getMessage(), e);
}
} else if (grammar.isLeft()) {
ixmlGrammar = grammar.left().get().getStringValue();
} else {
// grammar is an element: serialize it to a String
try (final StringBuilderWriter writer = new StringBuilderWriter()) {
final XQuerySerializer xqSerializer = new XQuerySerializer(
context.getBroker(), new Properties(), writer);
xqSerializer.serialize((Sequence) grammar.right().get());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would serialization be avoidable if we used CoffeePot instead of Blitz?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it would still happen.

ixmlGrammar = writer.toString();
} catch (final SAXException e) {
throw new XPathException(this, ErrorCodes.W3CErrorCode.FOIX0001.getErrorCode(), e.getMessage(), e);
}
}

final boolean failOnError = options.contains(FAIL_ON_ERROR_KEY)
&& options.get(FAIL_ON_ERROR_KEY).effectiveBooleanValue();

final Parser parser;
try {
parser = Blitz.generate(ixmlGrammar);
} catch (final BlitzException e) {
throw new XPathException(this, ErrorCodes.W3CErrorCode.FOIX0001.getErrorCode(), e.getMessage(), e);
}

// parse the input using the ixml grammar
final String generatedXML;
try {
generatedXML = failOnError
? parser.parse(input, Blitz.Option.FAIL_ON_ERROR)
: parser.parse(input);
} catch (final BlitzException e) {
throw new XPathException(this, ErrorCodes.W3CErrorCode.FOIX0002.getErrorCode(), e.getMessage(), e);
}

return parse(generatedXML);
}

@Override
public void accept(final ExpressionVisitor visitor) {
if (visited) {
return;
}
visited = true;
}

private Sequence parse(final String xmlContent) throws XPathException {
final XMLReaderPool pool = context.getBroker().getBrokerPool().getParserPool();
final SAXAdapter adapter = new SAXAdapter(context);
final XMLReader reader = pool.borrowXMLReader();
try (final StringReader stringReader = new StringReader(xmlContent)) {
reader.setContentHandler(adapter);
reader.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter);
reader.parse(new InputSource(stringReader));
return adapter.getDocument();
} catch (final SAXException | IOException e) {
throw new XPathException(this, e.getMessage(), e);
} finally {
pool.returnXMLReader(reader);
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ public class FnModule extends AbstractInternalModule {
new FunctionDef(FnRandomNumberGenerator.FS_RANDOM_NUMBER_GENERATOR[0], FnRandomNumberGenerator.class),
new FunctionDef(FnRandomNumberGenerator.FS_RANDOM_NUMBER_GENERATOR[1], FnRandomNumberGenerator.class),
new FunctionDef(FunContainsToken.FS_CONTAINS_TOKEN[0], FunContainsToken.class),
new FunctionDef(FunContainsToken.FS_CONTAINS_TOKEN[1], FunContainsToken.class)
new FunctionDef(FunContainsToken.FS_CONTAINS_TOKEN[1], FunContainsToken.class),
new FunctionDef(FnInvisibleXml.FS_INVISIBLE_XML, FnInvisibleXml.class)
};

static {
Expand Down
Loading
Loading