diff --git a/exist-core/pom.xml b/exist-core/pom.xml index 677e244476..57fcc2ff3f 100644 --- a/exist-core/pom.xml +++ b/exist-core/pom.xml @@ -584,6 +584,12 @@ + + de.bottlecaps + markup-blitz + 1.12 + + xyz.elemental.fork.org.exist-db diff --git a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java index 6183cc35b2..067cbd0ec1 100644 --- a/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java +++ b/exist-core/src/main/java/org/exist/xquery/ErrorCodes.java @@ -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; @@ -1537,6 +1540,24 @@ public DynamicErrorCode(final QName qname, @Nullable final String description) { @Deprecated public static final ErrorCode FOXT0006 = W3CErrorCode.FOXT0006.errorCode; + /** + * @deprecated Use {@link W3CErrorCode#FOIX0001}. + */ + @Deprecated + public static final ErrorCode FOIX0001 = W3CErrorCode.FOIX0001.errorCode; + + /** + * @deprecated Use {@link W3CErrorCode#FOIX0002}. + */ + @Deprecated + public static final ErrorCode FOIX0002 = W3CErrorCode.FOIX0002.errorCode; + + /** + * @deprecated Use {@link W3CErrorCode#FOIX0003}. + */ + @Deprecated + public static final ErrorCode FOIX0003 = W3CErrorCode.FOIX0003.errorCode; + /** * @deprecated Use {@link W3CErrorCode#XTSE0165}. */ diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnInvisibleXml.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnInvisibleXml.java new file mode 100644 index 0000000000..cceb2c91de --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnInvisibleXml.java @@ -0,0 +1,163 @@ +/* + * 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.exist.dom.memtree.SAXAdapter; +import org.exist.util.XMLReaderPool; +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 javax.annotation.Nullable; +import java.io.IOException; +import java.io.StringReader; + +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]; + @Nullable final MapType options; + if (optionsArg.isEmpty()) { + options = null; + } else { + options = optionsArg.itemAt(0).toJavaObject(MapType.class); + } + + final IxmlParserFunction fn; + final Sequence grammarArg = args[0]; + if (grammarArg.isEmpty()) { + // no grammar provided + fn = new IxmlParserFunction(context, (StringValue) null, 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 grammarElement = grammarArg.itemAt(0).toJavaObject(Element.class); + fn = new IxmlParserFunction(context, grammarElement, options); + } + + final InlineFunction functionResult = new InlineFunction(context, fn); + return functionResult.eval(contextSequence, null); + } + + 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, + "Gets the next random number generator.", + returns(Type.DOCUMENT, "just a random string for now"), + param("Parser inpuit", Type.STRING, "param description") + ); + + @Nullable final Either grammar; + @Nullable final MapType options; + + IxmlParserFunction(final XQueryContext context, @Nullable final StringValue grammar, @Nullable final MapType options) { + super(context, FS_PARSE_INVISIBLE_XML); + this.grammar = Left(grammar); + this.options = options; + } + + IxmlParserFunction(final XQueryContext context, @Nullable final Element grammar, @Nullable final MapType options) { + super(context, FS_PARSE_INVISIBLE_XML); + 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 = getArguments(contextSequence, contextItem)[0]; + final String input = inputArg.itemAt(0).getStringValue(); + + // generate the default ixml grammar + final String ixmlGrammar; + if (grammar == null) { + ixmlGrammar = Blitz.ixmlGrammar(); + } else if (grammar.isLeft()) { + ixmlGrammar = grammar.left().get().getStringValue(); + } else { + // TODO(YB) use XQuerySerializer class to serialize Element to String + ixmlGrammar = ... + } + + // TODO(YB) set any options + + // parse the input using the ixml grammar + final String generatedXML = Blitz.generate(ixmlGrammar).parse(input); + + return parse(generatedXML); + } + + 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) { + // TODO(YB) add error code + throw new XPathException(this, e.getMessage(), e); + } finally { + pool.returnXMLReader(reader); + } + + } + } +} diff --git a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java index e801e9ce75..16367be1c4 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java @@ -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 {