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
58d0a9c
[feature] Create the function stub and add to the fn namespace
marmoure Sep 7, 2026
caec4f3
[feature] Add error codes.
marmoure Sep 8, 2026
939573f
[bugfix] Typo
marmoure Sep 9, 2026
e63d593
[feature] Use markup blitz for the IXML grammar parsing
marmoure Sep 9, 2026
dc93784
[feature] First attempt at creating a User defined function and retur…
marmoure Sep 9, 2026
d516e21
[feature] The inner function can take input now, and it retuns it as …
marmoure Sep 9, 2026
1584f3c
[feature] Use the default IXML parser to parse input and return it a…
marmoure Sep 9, 2026
fe29b7c
[feature] Build a document from the Blitz grammar response.
marmoure Sep 10, 2026
e841762
Review and discussion
adamretter Sep 10, 2026
0549900
[WIP] Trying to resolve args and get function call running
marmoure Sep 11, 2026
1260ebe
[wip] The 3 paths are working
marmoure Sep 14, 2026
1293491
[feature] Set the fail on error option
marmoure Sep 14, 2026
4e5ea6a
[feature] Use MapType from the xquery functions instead
marmoure Sep 14, 2026
ac07234
[feature] Add error codes
marmoure Sep 14, 2026
70300c8
[bugfix] Update the inner function description.
marmoure Sep 15, 2026
7888ee5
[test] Create unit test for the invisible-xml function
marmoure Sep 15, 2026
72c0b8c
[bugfix] Make sure the param is registered
marmoure Sep 16, 2026
61bb9af
[bugfix] clean up
marmoure Sep 17, 2026
63332c8
[feature] Remove the deprecated error codes and use new w3c error code
marmoure Sep 22, 2026
91c2f82
[refactor] Remove xquery test case comment from unit tests file
marmoure Sep 22, 2026
664f044
[bugfix] Static variables should be upper-case
marmoure Sep 22, 2026
62beb76
[bugfix] Refrence the actual error code instead of reproducing it as …
marmoure Sep 22, 2026
be1d393
[test] test case when no grammar was provided
marmoure Sep 22, 2026
13e537b
[bugfix] Fix the license headers
marmoure Sep 22, 2026
5a6bdf1
[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

</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)?

<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.

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?

// 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?

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