-
Notifications
You must be signed in to change notification settings - Fork 5
Add iXML support #239
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
base: develop-7.x.x
Are you sure you want to change the base?
Add iXML support #239
Changes from all commits
58d0a9c
caec4f3
939573f
e63d593
dc93784
d516e21
1584f3c
fe29b7c
e841762
0549900
1260ebe
1293491
4e5ea6a
ac07234
70300c8
7888ee5
72c0b8c
61bb9af
63332c8
91c2f82
664f044
62beb76
be1d393
13e537b
5a6bdf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -584,6 +584,12 @@ | |
| <!-- scope>test</scope --> <!-- not just test scope, as needed for org.exist.test --> | ||
| </dependency> | ||
|
|
||
| <dependency> | ||
| <groupId>de.bottlecaps</groupId> | ||
| <artifactId>markup-blitz</artifactId> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
|
@@ -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> | ||
|
|
||
|
|
@@ -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> | ||
|
|
||
| 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the moment, What we really have in the code at the moment is: I think it might be better to change the code so that |
||
| 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure that |
||
| // 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()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
| } | ||
|
|
||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.xmlplease