// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.graphview.core.access;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.openstreetmap.josm.plugins.graphview.core.data.Tag;
import org.openstreetmap.josm.plugins.graphview.core.util.TagCondition;
import org.openstreetmap.josm.plugins.graphview.core.util.TagConditionLogic;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* class to be used in SAXHandler implementations for reading implication sections of xml files
*/
public class ImplicationXMLReader {
private final List<Implication> implications = new LinkedList<>();
private enum State { BEFORE_IMPLICATION, BEFORE_CONDITION, CONDITION, BEFORE_IMPLIES, IMPLIES, AFTER_IMPLIES }
private State state = State.BEFORE_IMPLICATION;
private ConditionReader currentConditionReader;
private TagCondition currentCondition;
private Collection<Tag> currentImpliedTags;
boolean tagOpen = false;
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
switch (state) {
case BEFORE_IMPLICATION:
if ("implication".equals(name)) {
state = State.BEFORE_CONDITION;
return;
}
break;
case BEFORE_CONDITION:
if ("condition".equals(name)) {
currentConditionReader = new ConditionReader();
state = State.CONDITION;
return;
}
break;
case CONDITION:
currentConditionReader.startElement(uri, localName, name, attributes);
return;
case BEFORE_IMPLIES:
if ("implies".equals(name)) {
currentImpliedTags = new LinkedList<>();
state = State.IMPLIES;
return;
}
break;
case IMPLIES:
if ("tag".equals(name)) {
if (tagOpen) {
throw new SAXException(tr("Tag element inside other tag element"));
}
currentImpliedTags.add(readTag(attributes));
tagOpen = true;
return;
}
break;
default:
break;
}
//all valid paths end with return; reaching this indicates an invalid tag
throw new SAXException(tr("Invalid opening xml tag <{0}> in state {1}", name, state));
}
public void endElement(String uri, String localName, String name) throws SAXException {
switch (state) {
case CONDITION:
if (name.equals("condition")) {
if (!currentConditionReader.isFinished()) {
throw new SAXException(tr("Condition isn''t finished at </condition> tag"));
} else {
currentCondition = currentConditionReader.getCondition();
currentConditionReader = null;
state = State.BEFORE_IMPLIES;
return;
}
} else {
currentConditionReader.endElement(uri, localName, name);
return;
}
case IMPLIES:
if (name.equals("implies")) {
state = State.AFTER_IMPLIES;
return;
} else if (name.equals("tag")) {
if (!tagOpen) {
throw new SAXException(tr("Closing tag element that was not open"));
}
tagOpen = false;
return;
}
break;
case AFTER_IMPLIES:
if (name.equals("implication")) {
implications.add(new Implication(currentCondition, currentImpliedTags));
currentCondition = null;
currentImpliedTags = null;
state = State.BEFORE_IMPLICATION;
return;
}
break;
default:
break;
}
//all valid paths end with return; reaching this indicates an invalid tag
throw new SAXException(tr("Invalid closing xml tag </{0}> in state {1}", name, state));
}
public List<Implication> getImplications() throws SAXException {
if (state != State.BEFORE_IMPLICATION) {
throw new SAXException(tr("Some tags have not been closed; now in state {0}", state));
} else {
return new ArrayList<>(implications);
}
}
private static Tag readTag(Attributes attributes) throws SAXException {
String key = attributes.getValue("k");
String value = attributes.getValue("v");
if (key == null) {
throw new SAXException(tr("Tag without key"));
} else if (value == null) {
throw new SAXException(tr("Tag without value (key is {0})", key));
}
return new Tag(key, value);
}
private static String readKey(Attributes attributes) throws SAXException {
String key = attributes.getValue("k");
if (key == null) {
throw new SAXException(tr("Key element without attribute k"));
}
return key;
}
/**
* class to be used for reading tag condition sections of xml files
*/
private static class ConditionReader {
String openingName;
TagCondition condition;
boolean finished;
private final List<ConditionReader> childReaders = new LinkedList<>();
private ConditionReader currentChildReader = null;
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
if (finished) {
throw new SAXException(tr("Condition is already finished at <{0}>", name));
}
if (currentChildReader != null) {
currentChildReader.startElement(uri, localName, name, attributes);
return;
}
//first tag is start tag of this condition
if (openingName == null) {
openingName = name;
if ("tag".equals(name)) {
condition = TagConditionLogic.tag(readTag(attributes));
} else if ("key".equals(name)) {
condition = TagConditionLogic.key(readKey(attributes));
} else if (!("or".equals(name)) && !("and".equals(name)) && !("not".equals(name))) {
throw new SAXException(tr("Unknown tag for condition: {0}", name));
}
//all tags after the first are start tags of child conditions
} else {
if ("tag".equals(openingName) || "key".equals(openingName)) {
throw new SAXException(tr("Element must not have children: {0}", openingName));
}
currentChildReader = new ConditionReader();
currentChildReader.startElement(uri, localName, name, attributes);
}
}
public void endElement(String uri, String localName, String name) throws SAXException {
if (finished) {
throw new SAXException(tr("Condition is already finished at </{0}>", name));
}
/* if active child reader exists, pass parameter to it. */
if (currentChildReader != null) {
currentChildReader.endElement(uri, localName, name);
if (currentChildReader.isFinished()) {
childReaders.add(currentChildReader);
currentChildReader = null;
}
} else {
if (openingName.equals(name)) {
List<TagCondition> childConditions = new ArrayList<>();
for (ConditionReader childReader : childReaders) {
childConditions.add(childReader.getCondition());
}
if ("and".equals(openingName)) {
if (childConditions.size() > 0) {
condition = TagConditionLogic.and(childConditions);
} else {
throw new SAXException(tr("<and> needs at least one child"));
}
} else if ("or".equals(openingName)) {
if (childConditions.size() > 0) {
condition = TagConditionLogic.or(childConditions);
} else {
throw new SAXException(tr("<or> needs at least one child"));
}
} else if ("not".equals(openingName)) {
if (childConditions.size() == 1) {
condition = TagConditionLogic.not(childConditions.get(0));
} else {
throw new SAXException(tr("<not> needs at least one child"));
}
}
finished = true;
} else {
throw new SAXException(tr("Wrong closing tag {0} (</{1}> expected)", name, openingName));
}
}
}
public boolean isFinished() {
return finished;
}
public TagCondition getCondition() {
if (!finished) {
throw new IllegalStateException(tr("Condition {0} not yet finished", openingName));
} else {
assert condition != null;
return condition;
}
}
}
}