Skip to content

Commit

Permalink
Merge pull request #775 from seadowg/partials
Browse files Browse the repository at this point in the history
Add partial elements
  • Loading branch information
seadowg authored Jul 10, 2024
2 parents 45a73a1 + 744e8a2 commit 882d0f8
Show file tree
Hide file tree
Showing 11 changed files with 370 additions and 35 deletions.
1 change: 0 additions & 1 deletion PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,5 @@ Inspect external instances (their ID and parsed XML) after parsing or provide cu

### API
- `ExternalInstanceParser#addFileInstanceParser`
- `ExternalInstanceparsser#addProcessor`

The default `ExternalInstanceParser` can be overridden by creating an implementation of `ExternalInstanceParserFactory` and calling `XFormUtils.setExternalInstanceParserFactory` with it.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.apache.commons.io.input.BOMInputStream;
import org.javarosa.core.model.data.UncastData;
import org.javarosa.xform.parse.ExternalInstanceParser;
import org.jetbrains.annotations.NotNull;

import java.io.BufferedReader;
import java.io.FileInputStream;
Expand All @@ -16,7 +17,7 @@

public class CsvExternalInstance implements ExternalInstanceParser.FileInstanceParser {

public TreeElement parse(String instanceId, String path) throws IOException {
public TreeElement parse(@NotNull String instanceId, @NotNull String path) throws IOException {
final TreeElement root = new TreeElement("root", 0);
root.setInstanceName(instanceId);

Expand Down
30 changes: 24 additions & 6 deletions src/main/java/org/javarosa/core/model/instance/DataInstance.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package org.javarosa.core.model.instance;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.javarosa.core.model.IDataReference;
import org.javarosa.core.services.storage.Persistable;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.ExtWrapNullable;
import org.javarosa.core.util.externalizable.PrototypeFactory;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
* A data instance represents a tree structure of abstract tree
* elements which can be accessed and read with tree references. It is
Expand Down Expand Up @@ -81,6 +81,10 @@ public T resolveReference(TreeReference ref) {
AbstractTreeElement<T> node = getBase();
T result = null;
for (int i = 0; i < ref.size(); i++) {
if (node instanceof TreeElement && ((TreeElement) node).isPartial()) {
throw new PartialElementEncounteredException();
}

String name = ref.getName(i);
int mult = ref.getMultiplicity(i);

Expand Down Expand Up @@ -299,4 +303,18 @@ public void setID(int recordid) {

public abstract void initialize(InstanceInitializationFactory initializer, String instanceId);

public void replacePartialElements(List<TreeElement> elements) {
for (TreeElement element : elements) {
TreeElement root = (TreeElement) getRoot();
TreeElement matchingChild = root.getChild(element.getName(), element.getMultiplicity());

if (matchingChild != null) {
matchingChild.populatePartial(element);
}
}
}

public static class PartialElementEncounteredException extends RuntimeException {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static ExternalDataInstance build(String instanceSrc, String instanceId)
throws IOException, UnfullfilledRequirementsException, XmlPullParserException, InvalidStructureException {
TreeElement root;
try {
root = parseExternalInstance(instanceSrc, instanceId);
root = XFormUtils.getExternalInstance(ReferenceManager.instance(), instanceId, instanceSrc, true);

// Avoid parse error for missing name and label refs if a select is built on an empty placeholder file
if (!root.hasChildren()) {
Expand All @@ -76,9 +76,20 @@ public static ExternalDataInstance build(String instanceSrc, String instanceId)
return new ExternalDataInstance(root, instanceId, instanceSrc);
}

private static TreeElement parseExternalInstance(String instanceSrc, String instanceId)
throws IOException, InvalidReferenceException, InvalidStructureException, XmlPullParserException, UnfullfilledRequirementsException {
return XFormUtils.getExternalInstance(ReferenceManager.instance(), instanceId, instanceSrc);
@Override
public AbstractTreeElement resolveReference(TreeReference ref) {
try {
return super.resolveReference(ref);
} catch (PartialElementEncounteredException e) {
try {
parseExternalFile(false);
} catch (InvalidReferenceException | InvalidStructureException | XmlPullParserException |
UnfullfilledRequirementsException | IOException exception) {
throw new RuntimeException(new DeserializationException("Unable to parse external instance: " + exception));
}

return resolveReference(ref);
}
}

@Override
Expand Down Expand Up @@ -114,7 +125,7 @@ public void readExternal(DataInputStream in, PrototypeFactory pf)
super.readExternal(in, pf);
path = ExtUtil.readString(in);
try {
setRoot(parseExternalInstance(path, getInstanceId()));
parseExternalFile(true);
} catch (InvalidReferenceException | InvalidStructureException | XmlPullParserException |
UnfullfilledRequirementsException e) {
throw new DeserializationException("Unable to parse external instance: " + e);
Expand All @@ -126,4 +137,9 @@ public void writeExternal(DataOutputStream out) throws IOException {
super.writeExternal(out);
ExtUtil.write(out, path);
}

private void parseExternalFile(boolean partial) throws UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, IOException, InvalidReferenceException {
String instanceId = getInstanceId();
setRoot(XFormUtils.getExternalInstance(ReferenceManager.instance(), instanceId, path, partial));
}
}
22 changes: 22 additions & 0 deletions src/main/java/org/javarosa/core/model/instance/TreeElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public class TreeElement implements Externalizable, AbstractTreeElement<TreeElem
* instance or null for the primary instance.
*/
private String instanceName = null;
private boolean isPartial;

/**
* TreeElement with null name and 0 multiplicity? (a "hidden root" node?)
Expand All @@ -125,6 +126,11 @@ public TreeElement(String name, int multiplicity) {
attributes = new ArrayList<TreeElement>(0);
}

public TreeElement(String name, int multiplicity, boolean isPartial) {
this(name, multiplicity);
this.isPartial = isPartial;
}

/**
* Construct a TreeElement which represents an attribute with the provided
* namespace and name.
Expand Down Expand Up @@ -1141,4 +1147,20 @@ public String getNamespacePrefix() {
public void setNamespacePrefix(String namespacePrefix) {
this.namespacePrefix = namespacePrefix;
}

public boolean isPartial() {
return isPartial;
}

public void populatePartial(TreeElement element) {
if (isPartial) {
children.clear();

for (int i = 0; i < element.getNumChildren(); i++) {
addChild(element.getChildAt(i));
}

isPartial = false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.xform.parse.ExternalInstanceParser;
import org.jetbrains.annotations.NotNull;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Objects;

public class GeoJsonExternalInstance implements ExternalInstanceParser.FileInstanceParser {

public TreeElement parse(String instanceId, String path) throws IOException {
public TreeElement parse(@NotNull String instanceId, @NotNull String path) throws IOException {
final TreeElement root = new TreeElement("root", 0);
root.setInstanceName(instanceId);

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/javarosa/test/TempFileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ static File createTempDir(String name) {
return subDir;
}

static File createTempFile(String prefix, String suffix) {
public static File createTempFile(String prefix, String suffix) {
return createTempFile(null, prefix, suffix);
}

Expand Down
28 changes: 9 additions & 19 deletions src/main/java/org/javarosa/xform/parse/ExternalInstanceParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand All @@ -22,13 +21,12 @@

public class ExternalInstanceParser {

private List<ExternalDataInstanceProcessor> externalDataInstanceProcessors = new ArrayList<>();
private List<FileInstanceParser> fileInstanceParsers = asList(
new CsvExternalInstance(),
new GeoJsonExternalInstance()
);

public TreeElement parse(ReferenceManager referenceManager, String instanceId, String instanceSrc) throws IOException, UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, InvalidReferenceException {
public TreeElement parse(ReferenceManager referenceManager, String instanceId, String instanceSrc, boolean partial) throws IOException, UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, InvalidReferenceException {
String path = getPath(referenceManager, instanceSrc);

Optional<FileInstanceParser> fileParser = fileInstanceParsers.stream()
Expand All @@ -37,20 +35,15 @@ public TreeElement parse(ReferenceManager referenceManager, String instanceId, S

TreeElement root;
if (fileParser.isPresent()) {
root = fileParser.get().parse(instanceId, path);
root = fileParser.get().parse(instanceId, path, partial);
} else {
root = XmlExternalInstance.parse(instanceId, path);
}

for (ExternalDataInstanceProcessor processor : externalDataInstanceProcessors) {
processor.processInstance(instanceId, root);
}

return root;
}

public void addProcessor(Processor processor) {
externalDataInstanceProcessors.add((ExternalDataInstanceProcessor) processor);
public TreeElement parse(ReferenceManager referenceManager, String instanceId, String instanceSrc) throws IOException, UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, InvalidReferenceException {
return parse(referenceManager, instanceId, instanceSrc, false);
}

/**
Expand All @@ -75,16 +68,13 @@ private static String getPath(ReferenceManager referenceManager, String srcLocat
return uri.startsWith("//") /* todo why is this? */ ? uri.substring(1) : uri;
}

public interface Processor {

}
public interface FileInstanceParser {
TreeElement parse(@NotNull String instanceId, @NotNull String path) throws IOException;

public interface ExternalDataInstanceProcessor extends ExternalInstanceParser.Processor {
void processInstance(@NotNull String id, @NotNull TreeElement root);
}
default TreeElement parse(@NotNull String instanceId, @NotNull String path, boolean partial) throws IOException {
return parse(instanceId, path);
}

public interface FileInstanceParser {
TreeElement parse(String instanceId, String path) throws IOException;
boolean isSupported(String instanceId, String instanceSrc);
}
}
13 changes: 13 additions & 0 deletions src/main/java/org/javarosa/xform/parse/XFormParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ public class XFormParser implements IXFormParserFunctions {
private final List<ModelAttributeProcessor> modelAttributeProcessors = new ArrayList<>();
private final List<QuestionProcessor> questionProcessors = new ArrayList<>();
private final List<XPathProcessor> xpathProcessors = new ArrayList<>();
private final List<ExternalDataInstanceProcessor> externalDataInstanceProcessors = new ArrayList<>();;

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

Expand Down Expand Up @@ -459,6 +460,10 @@ public void addProcessor(Processor processor) {
if (processor instanceof XPathProcessor) {
xpathProcessors.add((XPathProcessor) processor);
}

if (processor instanceof ExternalDataInstanceProcessor) {
externalDataInstanceProcessors.add((ExternalDataInstanceProcessor) processor);
}
}

public void addBindAttributeProcessor(BindAttributeProcessor bindAttributeProcessor) {
Expand Down Expand Up @@ -577,6 +582,10 @@ private void parseDoc(String formXmlSrc, Map<String, String> namespacePrefixesBy
ExternalDataInstance externalDataInstance;
try {
externalDataInstance = ExternalDataInstance.build(instanceSrc, instanceId);
for (ExternalDataInstanceProcessor processor : externalDataInstanceProcessors) {
processor.processInstance(externalDataInstance);
}

} catch (IOException | UnfullfilledRequirementsException | InvalidStructureException | XmlPullParserException e) {
String msg = "Unable to parse external secondary instance";
logger.error(msg, e);
Expand Down Expand Up @@ -2488,6 +2497,10 @@ public interface QuestionProcessor extends Processor {
void processQuestion(@NotNull QuestionDef question);
}

public interface ExternalDataInstanceProcessor extends Processor {
void processInstance(@NotNull ExternalDataInstance instance);
}

public static class ParseException extends Exception {

public ParseException() {
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/javarosa/xform/util/XFormUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,12 @@ public static FormDef getFormFromSerializedResource(String resource) {
return returnForm;
}

public static TreeElement getExternalInstance(ReferenceManager referenceManager, String id, String instanceSrc, boolean partial) throws UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, IOException, InvalidReferenceException {
return externalInstanceParserFactory.getExternalInstanceParser().parse(referenceManager, id, instanceSrc, partial);
}

public static TreeElement getExternalInstance(ReferenceManager referenceManager, String id, String instanceSrc) throws UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, IOException, InvalidReferenceException {
return externalInstanceParserFactory.getExternalInstanceParser().parse(referenceManager, id, instanceSrc);
return getExternalInstance(referenceManager, id, instanceSrc, false);
}

/////Parser Attribute warning stuff
Expand Down
Loading

0 comments on commit 882d0f8

Please sign in to comment.