Skip to content

Commit 1c515e7

Browse files
committed
simple implementation of ixml using Markup Blitz
1 parent 1e6737e commit 1c515e7

File tree

4 files changed

+555
-320
lines changed

4 files changed

+555
-320
lines changed

exist-core/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,11 @@
602602
<scope>test</scope>
603603
</dependency>
604604

605+
<dependency>
606+
<groupId>de.bottlecaps</groupId>
607+
<artifactId>markup-blitz</artifactId>
608+
<version>1.3</version>
609+
</dependency>
605610
</dependencies>
606611

607612
<build>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package org.exist.xquery.functions.fn;
2+
3+
import static org.exist.xquery.FunctionDSL.optParam;
4+
import static org.exist.xquery.FunctionDSL.param;
5+
import static org.exist.xquery.functions.fn.FnModule.functionSignature;
6+
7+
import java.io.BufferedReader;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.io.InputStreamReader;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.stream.Collectors;
14+
15+
import org.exist.xquery.BasicFunction;
16+
import org.exist.xquery.Cardinality;
17+
import org.exist.xquery.ErrorCodes;
18+
import org.exist.xquery.Expression;
19+
import org.exist.xquery.Function;
20+
import org.exist.xquery.FunctionCall;
21+
import org.exist.xquery.FunctionFactory;
22+
import org.exist.xquery.FunctionSignature;
23+
import org.exist.xquery.XPathException;
24+
import org.exist.xquery.XQueryContext;
25+
import org.exist.xquery.functions.map.MapType;
26+
import org.exist.xquery.value.BooleanValue;
27+
import org.exist.xquery.value.FunctionReference;
28+
import org.exist.xquery.value.FunctionReturnSequenceType;
29+
import org.exist.xquery.value.Sequence;
30+
import org.exist.xquery.value.StringValue;
31+
import org.exist.xquery.value.Type;
32+
33+
import de.bottlecaps.markup.Blitz;
34+
import de.bottlecaps.markup.blitz.Parser;
35+
36+
/**
37+
* Implementation of
38+
* fn:invisible-xml(
39+
* $grammar as item()? := (),
40+
* $options as map(*)? := {}
41+
* ) as fn(xs:string) as item()
42+
* @see https://qt4cg.org/specifications/xpath-functions-40/Overview.html#ixml-functions
43+
*
44+
* To do:
45+
* - Use the Markup Blitz serializer instead of parsing serialized XML.
46+
*/
47+
public class FnInvisibleXml extends BasicFunction
48+
{
49+
50+
private static final String FS_INVISIBLE_XML_NAME = "invisible-xml";
51+
52+
static final FunctionSignature FS_INVISIBLE_XML = functionSignature(
53+
FnInvisibleXml.FS_INVISIBLE_XML_NAME,
54+
"Returns an ixml parser from a grammar. The parser returns an XML representation of the input string as parsed by the provided grammar.",
55+
new FunctionReturnSequenceType(Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "A function that can be used to parse an input string."),
56+
optParam("grammar", Type.ITEM, "The ixml grammar used to generate the parser"),
57+
optParam("options", Type.MAP, "The options for the parser genarator and the parser itself")
58+
);
59+
60+
private static final String IXML_GRAMMAR_RESOURCE = "org/exist/xquery/lib/ixml.ixml";
61+
62+
63+
public FnInvisibleXml(final XQueryContext context, final FunctionSignature signature) {
64+
super(context, signature);
65+
}
66+
67+
@Override
68+
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
69+
// Handle $grammar and $options parameters.
70+
final String grammar;
71+
if (args[0].isEmpty()) {
72+
grammar = getIxmlGrammar();
73+
} else {
74+
grammar = ((StringValue)args[0].itemAt(0)).getStringValue();
75+
}
76+
final Blitz.Option[] options;
77+
if (args[1].isEmpty()) {
78+
options = new Blitz.Option[0];
79+
} else {
80+
options = getOptions((MapType) args[1].itemAt(0));
81+
}
82+
// Generate the Markup Blitz parser for the grammar.
83+
final Parser parser = Blitz.generate(grammar, options);
84+
// Make an IxmlParser function from the Markup Blitz parser. The signature is fn(xs:string) as item()
85+
FunctionSignature parserSignature = functionSignature(
86+
"generated-ixml-parser",
87+
"Generated ixml parser, only used internally",
88+
new FunctionReturnSequenceType(Type.ITEM, Cardinality.EXACTLY_ONE, "The result of parsing the input string"),
89+
param("input", Type.STRING, "The input string")
90+
);
91+
final IxmlParser ixmlParser = new IxmlParser(context, parserSignature, parser);
92+
// Make a function reference that can be used as the result.
93+
FunctionCall functionCall = FunctionFactory.wrap(context, ixmlParser);
94+
return new FunctionReference(functionCall);
95+
}
96+
97+
// Read the ixml grammar from a resource.
98+
private String getIxmlGrammar() throws XPathException {
99+
try (final InputStream ixmlGrammarStream = FnInvisibleXml.class.getClassLoader().getResourceAsStream(IXML_GRAMMAR_RESOURCE)) {
100+
if (ixmlGrammarStream == null) {
101+
throw new XPathException(ErrorCodes.FODC0002, "The ixml grammar resource cannot be found at "+IXML_GRAMMAR_RESOURCE);
102+
}
103+
try ( InputStreamReader ixmlGrammarStreamReader = new InputStreamReader(ixmlGrammarStream);
104+
BufferedReader reader = new BufferedReader(ixmlGrammarStreamReader)) {
105+
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
106+
}
107+
}
108+
catch (IOException e)
109+
{
110+
throw new XPathException(ErrorCodes.FODC0002, "The ixml grammar resource cannot be opened at "+IXML_GRAMMAR_RESOURCE, e);
111+
}
112+
}
113+
114+
// Get Markup Blitz options from the $options as map(*) parameter.
115+
private Blitz.Option[] getOptions(final MapType options) throws XPathException {
116+
List<Blitz.Option> optionsList = new ArrayList<Blitz.Option>();
117+
Sequence failOnError = options.get(new StringValue("fail-on-error"));
118+
if (!failOnError.isEmpty() && ((BooleanValue)failOnError.itemAt(0).convertTo(Type.BOOLEAN)).getValue()) {
119+
optionsList.add(Blitz.Option.FAIL_ON_ERROR);
120+
}
121+
return optionsList.stream().toArray(Blitz.Option[]::new);
122+
}
123+
124+
125+
/**
126+
* A BasicFunction for the generated ixml parser.
127+
*/
128+
private static final class IxmlParser extends BasicFunction {
129+
130+
private Parser parser;
131+
132+
public IxmlParser(XQueryContext context, FunctionSignature signature, Parser parser) throws XPathException
133+
{
134+
super(context, signature);
135+
this.parser = parser;
136+
// We must set the arguments, which is not done automatically from the signature.
137+
final List<Expression> ixmlParserArgs = new ArrayList<>(1);
138+
ixmlParserArgs.add(new Function.Placeholder(context));
139+
this.setArguments(ixmlParserArgs);
140+
}
141+
142+
@Override
143+
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException
144+
{
145+
final String input = ((StringValue)args[0].itemAt(0)).getStringValue();
146+
// Parse the input string.
147+
final String output = parser.parse(input);
148+
// The output is serialized XML, which we need to parse.
149+
ParsingFunctions xmlParser = new ParsingFunctions(context, ParsingFunctions.signatures[0]);
150+
final Sequence[] xmlParserArgs = new Sequence[1];
151+
xmlParserArgs[0] = new StringValue(output).toSequence();
152+
return xmlParser.eval(xmlParserArgs, contextSequence);
153+
}
154+
155+
}
156+
157+
}

0 commit comments

Comments
 (0)