Skip to content

Commit

Permalink
Merge pull request #733 from seadowg/func-detect
Browse files Browse the repository at this point in the history
Add ability to process xpath expressions during parse
  • Loading branch information
lognaturel committed Dec 1, 2023
2 parents 72bbbf3 + 77692dc commit bee6652
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 24 deletions.
73 changes: 50 additions & 23 deletions src/main/java/org/javarosa/xform/parse/XFormParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import org.javarosa.xml.util.UnfullfilledRequirementsException;
import org.javarosa.xpath.XPathConditional;
import org.javarosa.xpath.XPathParseTool;
import org.javarosa.xpath.expr.XPathExpression;
import org.javarosa.xpath.expr.XPathFuncExpr;
import org.javarosa.xpath.expr.XPathNumericLiteral;
import org.javarosa.xpath.expr.XPathPathExpr;
Expand Down Expand Up @@ -87,6 +88,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
Expand Down Expand Up @@ -180,6 +183,9 @@ public class XFormParser implements IXFormParserFunctions {
private final List<FormDefProcessor> formDefProcessors = new ArrayList<>();
private final List<ModelAttributeProcessor> modelAttributeProcessors = new ArrayList<>();
private final List<QuestionProcessor> questionProcessors = new ArrayList<>();
private final List<XPathProcessor> xpathProcessors = new ArrayList<>();

public static final List<XPathProcessor> tempXPathProcessors = new ArrayList<>();

/**
* The string IDs of all instances that are referenced in a instance() function call in the primary instance
Expand All @@ -197,6 +203,8 @@ public static IAnswerResolver getAnswerResolver() {
return answerResolver;
}

private static final Lock parseLock = new ReentrantLock();

public static void setAnswerResolver(IAnswerResolver answerResolver) {
XFormParser.answerResolver = answerResolver;
}
Expand Down Expand Up @@ -387,36 +395,47 @@ public FormDef parse() throws ParseException {
* no data will be loaded and the instance will be blank.
*/
public FormDef parse(String formXmlSrc, String lastSavedSrc) throws ParseException {
if (_f == null) {
logger.info("Parsing form...");

if (_xmldoc == null) {
try {
_xmldoc = getXMLDocument(_reader, stringCache);
} catch (IOException e) {
throw new ParseException("IO Exception during parse! " + e.getMessage());
}
try {
if (!parseLock.tryLock()) {
throw new IllegalStateException("Another XForm is being parsed!");
}

parseDoc(formXmlSrc, buildNamespacesMap(_xmldoc.getRootElement()), lastSavedSrc);
tempXPathProcessors.addAll(xpathProcessors);

if (_f == null) {
logger.info("Parsing form...");

if (_xmldoc == null) {
try {
_xmldoc = getXMLDocument(_reader, stringCache);
} catch (IOException e) {
throw new ParseException("IO Exception during parse! " + e.getMessage());
}
}

parseDoc(formXmlSrc, buildNamespacesMap(_xmldoc.getRootElement()), lastSavedSrc);

//load in a custom xml instance, if applicable
if (_instReader != null) {
try {
loadXmlInstance(_f, _instReader);
} catch (IOException e) {
throw new ParseException("IO Exception during parse! " + e.getMessage());
//load in a custom xml instance, if applicable
if (_instReader != null) {
try {
loadXmlInstance(_f, _instReader);
} catch (IOException e) {
throw new ParseException("IO Exception during parse! " + e.getMessage());
}
} else if (_instDoc != null) {
loadXmlInstance(_f, _instDoc);
}
} else if (_instDoc != null) {
loadXmlInstance(_f, _instDoc);
}
}

for (FormDefProcessor formDefProcessor : formDefProcessors) {
formDefProcessor.processFormDef(_f);
}
for (FormDefProcessor formDefProcessor : formDefProcessors) {
formDefProcessor.processFormDef(_f);
}

return _f;
return _f;
} finally {
tempXPathProcessors.clear();
parseLock.unlock();
}
}

public void addProcessor(Processor processor) {
Expand All @@ -435,6 +454,10 @@ public void addProcessor(Processor processor) {
if (processor instanceof QuestionProcessor) {
questionProcessors.add((QuestionProcessor) processor);
}

if (processor instanceof XPathProcessor) {
xpathProcessors.add((XPathProcessor) processor);
}
}

public void addBindAttributeProcessor(BindAttributeProcessor bindAttributeProcessor) {
Expand Down Expand Up @@ -2438,6 +2461,10 @@ public interface Processor {

}

public interface XPathProcessor extends Processor {
void processXPath(@NotNull XPathExpression xPathExpression);
}

public interface FormDefProcessor extends Processor {
void processFormDef(FormDef formDef) throws ParseException;
}
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/javarosa/xpath/XPathParseTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.javarosa.xpath;

import org.javarosa.xform.parse.XFormParser;
import org.javarosa.xpath.expr.XPathExpression;
import org.javarosa.xpath.parser.Lexer;
import org.javarosa.xpath.parser.Parser;
Expand All @@ -38,6 +39,11 @@ public class XPathParseTool {
};

public static XPathExpression parseXPath (String xpath) throws XPathSyntaxException {
return Parser.parse(Lexer.lex(xpath));
XPathExpression expression = Parser.parse(Lexer.lex(xpath));
for (XFormParser.XPathProcessor processor : XFormParser.tempXPathProcessors) {
processor.processXPath(expression);
}

return expression;
}
}
102 changes: 102 additions & 0 deletions src/test/java/org/javarosa/xform/parse/XPathProcessorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.javarosa.xform.parse;

import org.javarosa.core.util.XFormsElement;
import org.javarosa.xpath.expr.XPathExpression;
import org.javarosa.xpath.expr.XPathPathExpr;
import org.javarosa.xpath.expr.XPathQName;
import org.javarosa.xpath.expr.XPathStep;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.javarosa.core.util.BindBuilderXFormsElement.bind;
import static org.javarosa.core.util.XFormsElement.body;
import static org.javarosa.core.util.XFormsElement.head;
import static org.javarosa.core.util.XFormsElement.input;
import static org.javarosa.core.util.XFormsElement.mainInstance;
import static org.javarosa.core.util.XFormsElement.model;
import static org.javarosa.core.util.XFormsElement.t;

public class XPathProcessorTest {

@Test
public void processesXPathExpressions() throws Exception {
XFormsElement form = XFormsElement.html(
head(
model(
mainInstance(
t("data id=\"form\"",
t("question")
)
),
bind("/data/question").type("string")
)
),
body(
input("/data/question")
)
);

XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes())));
RecordingXPathProcessor processor = new RecordingXPathProcessor();
parser.addProcessor(processor);
parser.parse(null);

assertThat(processor.processedExpressions, contains(
new XPathPathExpr(XPathPathExpr.INIT_CONTEXT_ROOT, new XPathStep[]{
new XPathStep(XPathStep.AXIS_CHILD, new XPathQName("data")),
new XPathStep(XPathStep.AXIS_CHILD, new XPathQName("question"))
}),
new XPathPathExpr(XPathPathExpr.INIT_CONTEXT_ROOT, new XPathStep[]{
new XPathStep(XPathStep.AXIS_CHILD, new XPathQName("data")),
new XPathStep(XPathStep.AXIS_CHILD, new XPathQName("question"))
})
));
}

@Test
public void processorsAreNotRetainedBetweenParses() throws Exception {
XFormsElement form = XFormsElement.html(
head(
model(
mainInstance(
t("data id=\"form\"",
t("question")
)
),
bind("/data/question").type("string")
)
),
body(
input("/data/question")
)
);

XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes())));
RecordingXPathProcessor processor = new RecordingXPathProcessor();
parser.addProcessor(processor);
parser.parse(null);
assertThat(processor.processedExpressions.size(), equalTo(2));

XFormParser secondParser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes())));
secondParser.parse(null);
assertThat(processor.processedExpressions.size(), equalTo(2));
}

private static class RecordingXPathProcessor implements XFormParser.XPathProcessor {

public final List<XPathExpression> processedExpressions = new ArrayList<>();

@Override
public void processXPath(@NotNull XPathExpression xPathExpression) {
processedExpressions.add(xPathExpression);
}
}
}

0 comments on commit bee6652

Please sign in to comment.