Skip to content

Commit

Permalink
Merge pull request #782 from seadowg/parsing-path
Browse files Browse the repository at this point in the history
Add way of providing external instances without an existing file
  • Loading branch information
lognaturel committed Jul 17, 2024
2 parents b858d25 + 49d229b commit f829dc1
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 21 deletions.
1 change: 1 addition & 0 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Inspect the `FormEntryModel` after finalization (or "post processing") and attac
Inspect external instances (their ID and parsed XML) after parsing or provide custom parsers for specific instances or file types.

### API
- `ExternalInstanceParser#addInstanceProvider`
- `ExternalInstanceParser#addFileInstanceParser`

The default `ExternalInstanceParser` can be overridden by creating an implementation of `ExternalInstanceParserFactory` and calling `XFormUtils.setExternalInstanceParserFactory` with it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public TreeElement parse(@NotNull String instanceId, @NotNull String path) throw
}

@Override
public boolean isSupported(String instanceId, String instanceSrc) {
public boolean isSupported(@NotNull String instanceId, @NotNull String instanceSrc) {
return instanceSrc.contains("file-csv");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public TreeElement parse(@NotNull String instanceId, @NotNull String path) throw
}

@Override
public boolean isSupported(String instanceId, String instanceSrc) {
public boolean isSupported(@NotNull String instanceId, @NotNull String instanceSrc) {
return instanceSrc.endsWith("geojson");
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/javarosa/test/XFormsElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ static Map<String, String> parseAttributes(String name) {
Map<String, String> attributes = new HashMap<>();
String[] words = name.split(" ");
for (String word : asList(words).subList(1, words.length)) {
String[] parts = word.split("(?<!\\))=(\"|')");
String[] parts = word.split("(?<!\\))=(\"|')", 2);
attributes.put(parts[0], parts[1].substring(0, parts[1].length() - 1));
}
return attributes;
Expand Down
52 changes: 42 additions & 10 deletions src/main/java/org/javarosa/xform/parse/ExternalInstanceParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;

public class ExternalInstanceParser {

Expand All @@ -26,20 +27,30 @@ public class ExternalInstanceParser {
new GeoJsonExternalInstance()
);

public TreeElement parse(ReferenceManager referenceManager, String instanceId, String instanceSrc, boolean partial) throws IOException, UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, InvalidReferenceException {
String path = getPath(referenceManager, instanceSrc);
private List<InstanceProvider> instanceProviders = emptyList();

Optional<FileInstanceParser> fileParser = fileInstanceParsers.stream()
.filter(fileInstanceParser -> fileInstanceParser.isSupported(instanceId, instanceSrc))
public TreeElement parse(ReferenceManager referenceManager, String instanceId, String instanceSrc, boolean partial) throws IOException, UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, InvalidReferenceException {
Optional<InstanceProvider> instanceProvider = instanceProviders.stream()
.filter(parser -> parser.isSupported(instanceId, instanceSrc))
.findFirst();

TreeElement root;
if (fileParser.isPresent()) {
root = fileParser.get().parse(instanceId, path, partial);
if (instanceProvider.isPresent()) {
return instanceProvider.get().get(instanceId, instanceSrc, partial);
} else {
root = XmlExternalInstance.parse(instanceId, path);
String path = getPath(referenceManager, instanceSrc);

Optional<FileInstanceParser> fileParser = fileInstanceParsers.stream()
.filter(parser -> parser.isSupported(instanceId, instanceSrc))
.findFirst();

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

public TreeElement parse(ReferenceManager referenceManager, String instanceId, String instanceSrc) throws IOException, UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, InvalidReferenceException {
Expand All @@ -57,6 +68,17 @@ public void addFileInstanceParser(FileInstanceParser fileInstanceParser) {
).collect(Collectors.toList());
}

/**
* Adds {@link InstanceProvider} before others. The last added {@link InstanceProvider} will be checked
* (via {@link InstanceProvider#isSupported(String, String)}) first.
*/
public void addInstanceProvider(InstanceProvider instanceProvider) {
instanceProviders = Stream.concat(
Stream.of(instanceProvider),
instanceProviders.stream()
).collect(Collectors.toList());
}

/**
* Returns the path of the URI at srcLocation.
*
Expand All @@ -75,6 +97,16 @@ default TreeElement parse(@NotNull String instanceId, @NotNull String path, bool
return parse(instanceId, path);
}

boolean isSupported(String instanceId, String instanceSrc);
boolean isSupported(@NotNull String instanceId, @NotNull String instanceSrc);
}

public interface InstanceProvider {
TreeElement get(@NotNull String instanceId, @NotNull String instanceSrc) throws IOException;

default TreeElement get(@NotNull String instanceId, @NotNull String instanceSrc, boolean partial) throws IOException {
return get(instanceId, instanceSrc);
}

boolean isSupported(@NotNull String instanceId, @NotNull String instanceSrc);
}
}
97 changes: 89 additions & 8 deletions src/test/java/org/javarosa/plugins/InstancePluginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,61 @@ public void teardown() {
}

@Test
public void supportsPartialElements() throws IOException, XFormParser.ParseException {
public void instanceProvider_supportsPartialElements() throws IOException, XFormParser.ParseException {
externalInstanceParserFactory.setInstanceProvider(new FakeFileInstanceParser(asList(
new Pair<>("0", "Item 0"),
new Pair<>("1", "Item 1")
), true));

File tempFile = TempFileUtils.createTempFile("fake-instance", "fake");
setUpSimpleReferenceManager(tempFile, "file-csv", "file");

Scenario scenario = Scenario.init("Fake instance form", html(
head(
title("Fake instance form"),
model(
mainInstance(
t("data id=\"fake-instance-form\"",
t("question")
)
),
t("instance id=\"fake-instance\" src=\"jr://file-csv/fake-instance.fake\""),
bind("/data/question").type("string")
)
),
body(
select1Dynamic("/data/question", "instance('fake-instance')/root/item")
)
)
);

HashMap<String, DataInstance> instances = scenario.getFormDef().getFormInstances();
DataInstance fakeInstance = instances.get("fake-instance");
assertThat(fakeInstance.getRoot().getNumChildren(), equalTo(2));

TreeElement firstItem = (TreeElement) fakeInstance.getRoot().getChild("item", 0);
assertThat(firstItem.isPartial(), equalTo(true));
assertThat(firstItem.getNumChildren(), equalTo(2));
assertThat(firstItem.getChildAt(0).getName(), equalTo("value"));
assertThat(firstItem.getChildAt(0).getValue(), equalTo(null));
assertThat(firstItem.getChildAt(1).getName(), equalTo("label"));
assertThat(firstItem.getChildAt(1).getValue(), equalTo(null));

List<SelectChoice> selectChoices = scenario.choicesOf("/data/question");
assertThat(selectChoices.size(), equalTo(2));

assertThat(selectChoices.get(0).getValue(), equalTo("0"));
firstItem = (TreeElement) fakeInstance.getRoot().getChild("item", 0);
assertThat(firstItem.isPartial(), equalTo(false));
assertThat(firstItem.getNumChildren(), equalTo(2));
assertThat(firstItem.getChildAt(0).getName(), equalTo("value"));
assertThat(firstItem.getChildAt(0).getValue(), equalTo(new StringData("0")));
assertThat(firstItem.getChildAt(1).getName(), equalTo("label"));
assertThat(firstItem.getChildAt(1).getValue(), equalTo(new StringData("Item 0")));
}

@Test
public void fileInstanceParser_supportsPartialElements() throws IOException, XFormParser.ParseException {
externalInstanceParserFactory.setFileInstanceParser(new FakeFileInstanceParser(asList(
new Pair<>("0", "Item 0"),
new Pair<>("1", "Item 1")
Expand Down Expand Up @@ -211,20 +265,33 @@ public void replacePartialElements_DoesNotOverrideNonPartialElements() throws IO

private static class SwitchableExternalInstanceParserFactory implements ExternalInstanceParserFactory {
private ExternalInstanceParser.FileInstanceParser fileInstanceParser;
private ExternalInstanceParser.InstanceProvider instanceProvider;

@Override
public ExternalInstanceParser getExternalInstanceParser() {
ExternalInstanceParser externalInstanceParser = new ExternalInstanceParser();
externalInstanceParser.addFileInstanceParser(fileInstanceParser);

if (fileInstanceParser != null) {
externalInstanceParser.addFileInstanceParser(fileInstanceParser);
}

if (instanceProvider != null) {
externalInstanceParser.addInstanceProvider(instanceProvider);
}

return externalInstanceParser;
}

public void setFileInstanceParser(ExternalInstanceParser.FileInstanceParser fileInstanceParser) {
this.fileInstanceParser = fileInstanceParser;
}

public void setInstanceProvider(ExternalInstanceParser.InstanceProvider instanceProvider) {
this.instanceProvider = instanceProvider;
}
}

public static class FakeFileInstanceParser implements ExternalInstanceParser.FileInstanceParser {
public static class FakeFileInstanceParser implements ExternalInstanceParser.FileInstanceParser, ExternalInstanceParser.InstanceProvider {

private final List<Pair<String, String>> items;
private final boolean partialParse;
Expand All @@ -241,6 +308,25 @@ public TreeElement parse(@NotNull String instanceId, @NotNull String path) throw

@Override
public TreeElement parse(@NotNull String instanceId, @NotNull String path, boolean partial) throws IOException {
return createRoot(partial);
}

@Override
public TreeElement get(@NotNull String instanceId, @NotNull String instanceSrc) throws IOException {
return get(instanceId, instanceSrc, false);
}

@Override
public TreeElement get(@NotNull String instanceId, @NotNull String path, boolean partial) throws IOException {
return createRoot(partial);
}

@Override
public boolean isSupported(@NotNull String instanceId, @NotNull String instanceSrc) {
return instanceSrc.endsWith(".fake");
}

private @NotNull TreeElement createRoot(boolean partial) {
boolean isPartial = partialParse && partial;
TreeElement root = new TreeElement("root", 0);

Expand All @@ -262,10 +348,5 @@ public TreeElement parse(@NotNull String instanceId, @NotNull String path, boole

return root;
}

@Override
public boolean isSupported(String instanceId, String instanceSrc) {
return instanceSrc.endsWith(".fake");
}
}
}

0 comments on commit f829dc1

Please sign in to comment.