package org.commcare.xml;
import org.commcare.graph.util.GraphUtil;
import org.commcare.suite.model.Text;
import org.commcare.suite.model.graph.Annotation;
import org.commcare.suite.model.graph.BubbleSeries;
import org.commcare.suite.model.graph.Configurable;
import org.commcare.suite.model.graph.Graph;
import org.commcare.suite.model.graph.XYSeries;
import org.javarosa.xml.util.InvalidStructureException;
import org.javarosa.xpath.XPathParseTool;
import org.javarosa.xpath.parser.XPathSyntaxException;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* Created by jschweers on 1/28/2016.
*/
public class AndroidGraphParser extends GraphParser {
public AndroidGraphParser(KXmlParser parser) {
super(parser);
}
@Override
public Graph parse() throws InvalidStructureException, IOException, XmlPullParserException {
Graph graph = new Graph();
String type = parser.getAttributeValue(null, "type");
if (type == null) {
throw new InvalidStructureException("Expected attribute @type for element <" + parser.getName() + ">", parser);
}
graph.setType(type);
int entryLevel = parser.getDepth();
do {
// <graph> contains an optional <configuration>, 0 to many <series>,
// and 0 to many <annotation>, in any order.
parser.nextTag();
if (parser.getName().equals("configuration")) {
// There's no reason for a graph to have multiple <configuration> elements,
// but if it does, any later configuration settings will override earlier ones.
parseConfiguration(graph);
}
if (parser.getName().equals("series")) {
graph.addSeries(parseSeries(type));
}
if (parser.getName().equals("annotation")) {
parseAnnotation(graph);
}
} while (parser.getDepth() > entryLevel);
return graph;
}
/*
* Helper for parse; handles a single annotation, which must contain an x
* (which contains a single <text>), y (also contains a single <text>),
* and then another <text> for the annotation's actual text.
*/
private void parseAnnotation(Graph graph) throws InvalidStructureException, IOException, XmlPullParserException {
checkNode("annotation");
TextParser textParser = new TextParser(parser);
nextStartTag();
checkNode("x");
nextStartTag();
Text x = textParser.parse();
nextStartTag();
checkNode("y");
nextStartTag();
Text y = textParser.parse();
nextStartTag();
Text text = textParser.parse();
parser.nextTag();
graph.addAnnotation(new Annotation(x, y, text));
}
/*
* Helper for parse; handles a configuration element, which is a set of <text> elements, each with an id.
*/
private void parseConfiguration(Configurable data) throws InvalidStructureException, IOException, XmlPullParserException {
checkNode("configuration");
TextParser textParser = new TextParser(parser);
do {
parser.nextTag();
if (parser.getName().equals("text")) {
String id = parser.getAttributeValue(null, "id");
Text t = textParser.parse();
data.setConfiguration(id, t);
}
}
while (parser.getEventType() != KXmlParser.END_TAG || !parser.getName().equals("configuration"));
}
/*
* Helper for parse; handles a single series, which is an optional <configuration> followed by an <x>, a <y>,
* and, if this graph is a bubble graph, a <radius>.
*/
private XYSeries parseSeries(String type) throws InvalidStructureException, IOException, XmlPullParserException {
checkNode("series");
String nodeSet = parser.getAttributeValue(null, "nodeset");
XYSeries series = type.equals(GraphUtil.TYPE_BUBBLE) ? new BubbleSeries(nodeSet) : new XYSeries(nodeSet);
nextStartTag();
if (parser.getName().equals("configuration")) {
parseConfiguration(series);
nextStartTag();
}
checkNode("x");
series.setX(parseFunction("x"));
nextStartTag();
series.setY(parseFunction("y"));
if (type.equals(GraphUtil.TYPE_BUBBLE)) {
nextStartTag();
checkNode("radius");
((BubbleSeries)series).setRadius(parseFunction("radius"));
}
while (parser.getEventType() != KXmlParser.END_TAG || !parser.getName().equals("series")) {
parser.nextTag();
}
return series;
}
/**
* Get an XPath function from a node and attempt to parse it.
*
* @param name Node name, also used in any error message.
* @return String representation of the XPath function.
* @throws InvalidStructureException
*/
private String parseFunction(String name) throws InvalidStructureException {
checkNode(name);
String function = parser.getAttributeValue(null, "function");
try {
XPathParseTool.parseXPath(function);
} catch (XPathSyntaxException e) {
throw new InvalidStructureException("Invalid " + name + " function in graph: " + function + ". " + e.getMessage(), parser);
}
return function;
}
/*
* Move parser along until it hits a start tag.
*/
private void nextStartTag() throws IOException, XmlPullParserException {
do {
parser.nextTag();
} while (parser.getEventType() != KXmlParser.START_TAG);
}
}