/*
* AbstractXMLObjectParser.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.xml;
import dr.app.tools.BeastParserDoc;
import org.w3c.dom.NamedNodeMap;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public abstract class AbstractXMLObjectParser implements XMLObjectParser {
public final Object parseXMLObject(XMLObject xo, String id, Map<String, XMLObject> store, boolean strictXML)
throws XMLParseException {
this.store = store;
if (hasSyntaxRules()) {
final XMLSyntaxRule[] rules = getSyntaxRules();
for (XMLSyntaxRule rule : rules) {
if (!rule.isSatisfied(xo)) {
if (id != null) {
throw new XMLParseException("The '<" + getParserName() +
">' element, with id, '" + id +
"', is incorrectly constructed.\nThe following was expected:\n" +
rule.ruleString(xo));
} else {
String parentId = null;
XMLObject xop = xo;
while (parentId == null && xop != null) {
xop = xop.getParent();
if (xop != null && xop.hasId()) {
parentId = xop.getId();
}
}
throw new XMLParseException("The '<" + getParserName() +
">' element, nested within an element with id, '" + parentId +
"', is incorrectly constructed.\nThe following was expected:\n" +
rule.ruleString(xo));
}
}
}
// Look for undeclared attributes and issue a warning
final NamedNodeMap attributes = xo.getAttributes();
for (int k = 0; k < attributes.getLength(); ++k) {
String name = attributes.item(k).getNodeName();
if (name.equals(XMLObject.ID)) continue;
for (XMLSyntaxRule rule : rules) {
if (rule.containsAttribute(name)) {
name = null;
break;
}
}
if (name != null) {
final String msg = "Unhandled attribute (typo?) " + name + " in " + xo;
if (strictXML) {
throw new XMLParseException(msg);
}
// System.err.println("WARNING:" + msg);
java.util.logging.Logger.getLogger("dr.xml").warning(msg);
}
}
// try to catch out of place elements, placed either by mistake or from older incompatible files.
for (int k = 0; k < xo.getChildCount(); ++k) {
final Object child = xo.getChild(k);
String unexpectedName;
if (child instanceof XMLObject) {
final XMLObject ch = (XMLObject) child;
unexpectedName = !isAllowed(ch.getName()) ? ch.getName() : null;
final List<String> unexpected = isUnexpected(ch);
if (unexpected != null) {
String n = "";
for (int j = 0; j < unexpected.size(); j += 2) {
n = n + ", " + unexpected.get(j) + " in " + unexpected.get(j + 1);
}
if (unexpectedName == null) {
unexpectedName = n.substring(1, n.length());
} else {
unexpectedName = unexpectedName + n;
}
}
} else {
unexpectedName = child.getClass().getName();
for (XMLSyntaxRule rule : rules) {
if (rule.isLegalElementClass(child.getClass())) {
unexpectedName = null;
break;
}
}
}
if (unexpectedName != null) {
String msg = "Unexpected element in " + xo + ": " + unexpectedName;
if (strictXML) {
throw new XMLParseException(msg);
}
// System.err.println("WARNING: " + msg);
java.util.logging.Logger.getLogger("dr.xml").warning(msg);
}
}
}
try {
return parseXMLObject(xo);
} catch (XMLParseException xpe) {
throw new XMLParseException("Error parsing '<" + getParserName() +
">' element with id, '" + id + "':\n" +
xpe.getMessage());
}
}
public String[] getParserNames() {
return new String[]{getParserName()};
}
public final void throwUnrecognizedElement(XMLObject xo) throws XMLParseException {
throw new XMLParseException("Unrecognized element '<" + xo.getName() + ">' in element '<" + getParserName() + ">'");
}
public abstract Object parseXMLObject(XMLObject xo) throws XMLParseException;
/**
* @return an array of syntax rules required by this element.
* Order is not important.
*/
public abstract XMLSyntaxRule[] getSyntaxRules();
/**
* Allowed if any of the rules allows that element
*
* @param elementName String
* @return boolean isAllowed
*/
public final boolean isAllowed(String elementName) {
final XMLSyntaxRule[] rules = getSyntaxRules();
if (rules != null && rules.length > 0) {
for (XMLSyntaxRule rule : rules) {
if (rule.isLegalElementName(elementName)) {
return true;
}
}
}
return false;
}
public final List<String> isUnexpected(XMLObject element) {
List<String> un = null;
final XMLSyntaxRule[] rules = getSyntaxRules();
if (rules != null && rules.length > 0) {
for (XMLSyntaxRule rule : rules) {
if (rule.isLegalElementName(element.getName())) {
for (int nc = 0; nc < element.getChildCount(); ++nc) {
final Object child = element.getChild(nc);
if (child instanceof XMLObject) {
final String name = ((XMLObject) child).getName();
if (!rule.isLegalSubelementName(name)) {
if (un == null) {
un = new ArrayList<String>();
}
un.add(name);
un.add(element.getName());
}
}
}
}
}
}
return un;
}
public abstract String getParserDescription();
public abstract Class getReturnType();
public final boolean hasExample() {
return getExample() != null;
}
public String getExample() {
return null;
}
public final Map<String, XMLObject> getStore() {
return store;
}
/**
* @return a description of this parser as a string.
*/
public final String toHTML(XMLDocumentationHandler handler) {
StringBuffer buffer = new StringBuffer();
buffer.append("<div id=\"").append(getParserName()).append("\" class=\"element\">\n");
buffer.append(" <div class=\"elementheader\">\n");
buffer.append(" <span class=\"elementname\"><a href=\"").append(BeastParserDoc.INDEX_HTML)
.append("#").append(getParserName()).append("\"> <h3><").append(getParserName())
.append("></h3></a></span>\n");
buffer.append(" <div class=\"description\"><b>Description:</b><br>\n");
buffer.append(" ").append(getParserDescription()).append("\n");
buffer.append(" </div>\n");
buffer.append(" </div>\n");
if (hasSyntaxRules()) {
XMLSyntaxRule[] rules = getSyntaxRules();
buffer.append(" <div class=\"rules\"><b>Rule:</b>\n");
for (XMLSyntaxRule rule : rules) {
buffer.append(rule.htmlRuleString(handler));
}
buffer.append(" </div>\n");
}
if (hasExample()) {
buffer.append("<div class=\"example\"><b>Example:</b>");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
handler.outputExampleXML(pw, this);
pw.flush();
pw.close();
buffer.append(sw.toString());
buffer.append("</div>\n");
}
buffer.append("</div>\n");
return buffer.toString();
}
/**
* @return a description of this parser as a string.
*/
public final String toWiki(XMLDocumentationHandler handler) {
StringBuffer buffer = new StringBuffer();
buffer.append("===<code><").append(getParserName()).append("></code> element===\n\n");
buffer.append(getParserDescription()).append("\n\n");
if (hasSyntaxRules()) {
XMLSyntaxRule[] rules = getSyntaxRules();
List<XMLSyntaxRule> attributes = new ArrayList<XMLSyntaxRule>();
List<XMLSyntaxRule> contents = new ArrayList<XMLSyntaxRule>();
for (XMLSyntaxRule rule : rules) {
if (rule instanceof AttributeRule) {
attributes.add(rule);
} else {
contents.add(rule);
}
}
if (attributes.size() > 0) {
buffer.append("\nThe element takes following attributes:\n");
for (XMLSyntaxRule rule : attributes) {
buffer.append(rule.wikiRuleString(handler, "*"));
}
buffer.append("\n");
}
if (contents.size() > 0) {
buffer.append("\nThe element has the following contents:\n");
for (XMLSyntaxRule rule : contents) {
buffer.append(rule.wikiRuleString(handler, "*"));
}
buffer.append("\n");
}
}
buffer.append("\nExample:\n");
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
handler.outputExampleXML(pw, this);
pw.flush();
pw.close();
buffer.append(sw.toString());
buffer.append("\n");
return buffer.toString();
}
/**
* @return a description of this parser as a string.
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("\nELEMENT ").append(getParserName()).append("\n");
if (hasSyntaxRules()) {
XMLSyntaxRule[] rules = getSyntaxRules();
for (XMLSyntaxRule rule : rules) {
buffer.append(" ").append(rule.ruleString()).append("\n");
}
}
return buffer.toString();
}
//************************************************************************
// private methods
//************************************************************************
public final boolean hasSyntaxRules() {
XMLSyntaxRule[] rules = getSyntaxRules();
return (rules != null && rules.length > 0);
}
private Map<String, XMLObject> store = null;
}