// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.roadsigns;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.plugins.roadsigns.Sign.SignParameter;
import org.openstreetmap.josm.plugins.roadsigns.Sign.Tag;
import org.openstreetmap.josm.plugins.roadsigns.javacc.ParseException;
import org.openstreetmap.josm.plugins.roadsigns.javacc.TokenMgrError;
import org.openstreetmap.josm.tools.LanguageInfo;
import org.openstreetmap.josm.tools.XmlParsingException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Parses a road sign preset file.
*
*/
public class RoadSignsReader {
public static final String lang = LanguageInfo.getLanguageCodeXML();
private InputSource inputSource;
private final boolean debug = false;
/* counts the nesting level when we step into unknown elements */
private int unknownLevel;
public RoadSignsReader(InputStream source) throws IOException {
this.inputSource = new InputSource(new InputStreamReader(source, "UTF-8"));
}
private class SignParser extends DefaultHandler {
public List<Sign> allSigns;
public Map<Sign, List<String>> supplementIds;
String characters = "";
//List<Sign> folders = new ArrayList<Sign>();
Sign curSign;
Tag curTag;
private Locator locator;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
@Override public void startElement(String ns, String lname, String qname, Attributes atts) throws SAXException {
if (debug) System.err.println("<"+qname+">: ");
if (unknownLevel > 0) {
unknownLevel++;
} else if (qname.equals("roadsignpreset")) {
} else if (qname.equals("sign")) {
if (curSign != null)
throwException("found sign inside sign");
curSign = new Sign();
supplementIds.put(curSign, new ArrayList<String>());
try {
curSign.ref = ParametrizedString.create(atts.getValue("ref"));
curSign.traffic_sign_tag = ParametrizedString.create(atts.getValue("traffic_sign_tag"));
} catch (ParseException ex) {
throw new SAXException(ex);
} catch (TokenMgrError ex) {
throwException(ex.toString());
}
curSign.id = atts.getValue("id");
if (curSign.id == null) {
if (curSign.ref == null)
throwException("Both id and ref attribute missing.");
curSign.id = curSign.ref.toString();
}
curSign.name = getLocalized(atts, "name");
if (curSign.name == null)
throwException("missing attribute: name");
curSign.long_name = getLocalized(atts, "long_name");
String iconURL = atts.getValue("icon");
if (iconURL == null) {
iconURL = curSign.id;
iconURL = iconURL.replace(':', '_');
iconURL = iconURL.replace('.', '_');
}
curSign.iconURL = iconURL;
if ("yes".equals(atts.getValue("supplementary"))) {
curSign.isSupplementing = true;
}
curSign.wiki = atts.getValue("wiki");
curSign.loc_wiki = getLocalized(atts, "wiki");
curSign.help = getLocalized(atts, "help");
String useful = atts.getValue("useful");
if (useful != null) {
curSign.useful = Boolean.parseBoolean(useful);
}
} else if (curSign != null && qname.equals("tag")) {
if (curSign == null) {
throwException("found tag outside sign");
}
if (curTag != null) {
throwException("found tag inside tag");
}
curTag = new Tag();
curTag.ident = atts.getValue("ident");
curTag.tag_ref = atts.getValue("tag_ref");
try {
curTag.key = ParametrizedString.create(atts.getValue("key"));
curTag.value = ParametrizedString.create(atts.getValue("value"));
curTag.append_value = ParametrizedString.create(atts.getValue("append_value"));
curTag.condition = ParametrizedString.create(atts.getValue("condition"));
} catch (ParseException ex) {
throw new SAXException(ex);
} catch (TokenMgrError ex) {
throw new SAXException(ex.toString());
}
} else if (curSign != null && qname.equals("supplementary")) {
supplementIds.get(curSign).add(getMandatoryAttribute(atts, "id"));
} else if (curSign != null && qname.equals("parameter")) {
String inputType = getMandatoryAttribute(atts, "input");
SignParameter p = new SignParameter(inputType);
p.ident = getMandatoryAttribute(atts, "ident");
p.deflt = getMandatoryAttribute(atts, "default");
p.prefix = atts.getValue("prefix");
p.suffix = atts.getValue("suffix");
String width = atts.getValue("field_width");
if (width != null) {
p.fieldWidth = Integer.parseInt(width);
}
curSign.params.add(p);
} else {
warning("unknown element: "+qname);
unknownLevel++;
}
if (debug) {
for (int i = 0; i < atts.getLength(); ++i) {
System.err.println(" "+atts.getQName(i)+": "+atts.getValue(i));
}
}
}
@Override public void endElement(String ns, String lname, String qname) throws SAXException {
if (debug) System.err.print("</"+qname+"> |");
if (unknownLevel > 0) {
unknownLevel--;
} else if (qname.equals("sign")) {
allSigns.add(curSign);
curSign = null;
} else if (qname.equals("tag")) {
curSign.tags.add(curTag);
curTag = null;
}
characters = characters.trim();
if (!characters.equals("")) {
if (debug) System.err.print(characters);
characters = "";
}
if (debug) System.err.println("|");
}
@Override public void characters(char[] ch, int start, int length) {
String s = new String(ch, start, length);
characters += s;
}
private String getLocalized(Attributes atts, String ident) {
String result = atts.getValue(lang+ident);
if (result == null) {
result = atts.getValue(ident);
if (result == null)
return null;
result = tr(result);
}
return result;
}
private String getMandatoryAttribute(Attributes atts, String ident) throws XmlParsingException {
String result = atts.getValue(ident);
if (result == null) {
throwException("missing attribute: "+ident);
}
return result;
}
@Override
public void error(SAXParseException e) throws SAXException {
throwException(e);
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
throwException(e);
}
protected void throwException(Exception e) throws ExtendedParsingException {
throw new ExtendedParsingException(e).rememberLocation(locator);
}
protected void throwException(String msg) throws XmlParsingException {
throw new XmlParsingException(msg).rememberLocation(locator);
}
private void warning(String s) {
try {
throwException(s);
} catch (XmlParsingException ex) {
System.err.println("Warning: "+ex.getMessage());
}
}
public void wireSupplements() throws XmlParsingException {
Map<String, Sign> map = new HashMap<>();
for (Sign sign : allSigns) {
if (map.get(sign.id) != null)
throwException("Found 2 signs with same ref "+sign.ref);
map.put(sign.id, sign);
}
for (Sign sign : allSigns) {
for (String id : supplementIds.get(sign)) {
Sign supp = map.get(id);
if (supp == null)
throwException("Missing supplement definition "+id+" for sign "+sign.id+".");
sign.supplements.add(supp);
}
}
}
}
public static class ExtendedParsingException extends SAXException {
private int columnNumber;
private int lineNumber;
public ExtendedParsingException() {
super();
}
public ExtendedParsingException(Exception e) {
super(e);
}
public ExtendedParsingException(String message, Exception e) {
super(message, e);
}
public ExtendedParsingException(String message) {
super(message);
}
public ExtendedParsingException rememberLocation(Locator locator) {
if (locator == null) return this;
this.columnNumber = locator.getColumnNumber();
this.lineNumber = locator.getLineNumber();
return this;
}
@Override
public String getMessage() {
String msg = super.getMessage();
if (lineNumber == 0 && columnNumber == 0)
return msg;
System.err.println(".");
if (msg == null) {
msg = getClass().getName();
}
msg = msg + " " + tr("(at line {0}, column {1})", lineNumber, columnNumber);
return msg;
}
public int getColumnNumber() {
return columnNumber;
}
public int getLineNumber() {
return lineNumber;
}
}
/**
*
* @return True if file was properly parsed, false if there was error during parsing but some data were parsed anyway
*/
public List<Sign> parse() throws SAXException, IOException {
SignParser parser = new SignParser();
parser.allSigns = new ArrayList<>();
parser.supplementIds = new HashMap<>();
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.newSAXParser().parse(inputSource, parser);
parser.wireSupplements();
String filterPref = Main.pref.get("plugin.roadsigns.preset.filter");
if (filterPref.equals("useful")) {
List<Sign> filtered = new ArrayList<>();
for (Sign s : parser.allSigns) {
if (s.isUseful()) {
filtered.add(s);
}
}
return filtered;
} else {
return parser.allSigns;
}
} catch (ParserConfigurationException e) {
e.printStackTrace(); // broken SAXException chaining
throw new SAXException(e);
}
}
}