package com.kreative.paint.material.shape;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class PowerShapeParser {
public static PowerShapeList parse(String name, InputStream in) throws IOException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true); // make sure the XML is valid
factory.setExpandEntityReferences(false); // don't allow custom entities
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new SHPXEntityResolver());
builder.setErrorHandler(new SHPXErrorHandler(name));
Document document = builder.parse(new InputSource(in));
return parseDocument(document);
} catch (ParserConfigurationException pce) {
throw new IOException(pce);
} catch (SAXException saxe) {
throw new IOException(saxe);
}
}
private static PowerShapeList parseDocument(Node node) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("#document")) {
for (Node child : getChildren(node)) {
String ctype = child.getNodeName();
if (ctype.equalsIgnoreCase("shapes")) {
if (child.hasAttributes() || child.hasChildNodes()) {
return parseShapes(child);
}
} else {
throw new IOException("Unknown element: " + ctype);
}
}
throw new IOException("Empty document.");
} else {
throw new IOException("Unknown element: " + type);
}
}
private static PowerShapeList parseShapes(Node node) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("shapes")) {
NamedNodeMap attr = node.getAttributes();
String name = parseString(attr, "name");
PowerShapeList list = new PowerShapeList(name);
for (Node child : getChildren(node)) {
list.add(parseShape(child));
}
return list;
} else {
throw new IOException("Unknown element: " + type);
}
}
private static PowerShape parseShape(Node node) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("shape")) {
NamedNodeMap attr = node.getAttributes();
String name = parseString(attr, "name");
WindingRule winding = parseWindingRule(attr, "winding", WindingRule.NON_ZERO);
PowerShape shape = new PowerShape(winding, name);
for (Node child : getChildren(node)) {
String ctype = child.getNodeName();
if (ctype.equalsIgnoreCase("param")) {
shape.addParameter(parseParameter(child));
} else {
shape.addShape(parseSubshape(child));
}
}
return shape;
} else {
throw new IOException("Unknown element: " + type);
}
}
private static Parameter parseParameter(Node node) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("param")) {
NamedNodeMap attr = node.getAttributes();
return new Parameter(
parseString(attr, "name"),
parseDouble(attr, "origin-x", 0.0),
parseDouble(attr, "origin-y", 0.0),
parseBoolean(attr, "coords", "polar", "rectangular", false),
parseDouble(attr, "min-x", 0.0),
parseDouble(attr, "min-y", 0.0),
parseDouble(attr, "min-r", 0.0),
Math.toRadians(parseDouble(attr, "min-a", 0.0)),
parseDouble(attr, "def-x", 0.0),
parseDouble(attr, "def-y", 0.0),
parseDouble(attr, "def-r", 0.0),
Math.toRadians(parseDouble(attr, "def-a", 0.0)),
parseDouble(attr, "max-x", 0.0),
parseDouble(attr, "max-y", 0.0),
parseDouble(attr, "max-r", 0.0),
Math.toRadians(parseDouble(attr, "max-a", 0.0))
);
} else {
throw new IOException("Unknown element: " + type);
}
}
private static ParameterizedShape parseSubshape(Node node) throws IOException {
String type = node.getNodeName();
NamedNodeMap attr = node.getAttributes();
if (type.equalsIgnoreCase("arc")) {
return new ParameterizedShape.Arc(
parseValue(attr, "cx", 0.0),
parseValue(attr, "cy", 0.0),
parseValue(attr, "rx", 0.0),
parseValue(attr, "ry", 0.0),
parseValue(attr, "start", 0.0),
parseValue(attr, "extent", 0.0),
parseArcType(node)
);
} else if (type.equalsIgnoreCase("circle")) {
return new ParameterizedShape.Circle(
parseValue(attr, "cx", 0.0),
parseValue(attr, "cy", 0.0),
parseValue(attr, "r", 0.0)
);
} else if (type.equalsIgnoreCase("ellipse")) {
return new ParameterizedShape.Ellipse(
parseValue(attr, "cx", 0.0),
parseValue(attr, "cy", 0.0),
parseValue(attr, "rx", 0.0),
parseValue(attr, "ry", 0.0)
);
} else if (type.equalsIgnoreCase("line")) {
return new ParameterizedShape.Line(
parseValue(attr, "x1", 0.0),
parseValue(attr, "y1", 0.0),
parseValue(attr, "x2", 0.0),
parseValue(attr, "y2", 0.0)
);
} else if (type.equalsIgnoreCase("path")) {
return parsePath(node);
} else if (type.equalsIgnoreCase("polygon")) {
return new ParameterizedShape.Polygon(
parseValues(attr, "points")
);
} else if (type.equalsIgnoreCase("polyline")) {
return new ParameterizedShape.PolyLine(
parseValues(attr, "points")
);
} else if (type.equalsIgnoreCase("rect")) {
return new ParameterizedShape.Rect(
parseValue(attr, "x", 0.0),
parseValue(attr, "y", 0.0),
parseValue(attr, "width", 0.0),
parseValue(attr, "height", 0.0),
parseValue(attr, "rx", 0.0),
parseValue(attr, "ry", 0.0)
);
} else {
throw new IOException("Unknown element: " + type);
}
}
private static ParameterizedPath parsePath(Node node) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("path")) {
NamedNodeMap attr = node.getAttributes();
String d = parseString(attr, "d");
ParameterizedPath p = new ParameterizedPath();
if (d != null) {
try {
ExpressionLexer lexer = new ExpressionLexer(d);
ExpressionParser parser = new ExpressionParser(lexer);
while (lexer.hasNext()) {
char inst = lexer.getNext().charAt(0);
int n = ParameterizedPath.operandCount(inst);
ParameterizedValue[] v = new ParameterizedValue[n];
for (int i = 0; i < n; i++) {
int start = lexer.currentIndex();
Expression expr = parser.parseExpression();
int end = lexer.currentIndex();
String source = d.substring(start, end);
v[i] = new ParameterizedValue(source, expr);
}
p.add(inst, v);
}
} catch (ExpressionParserException e) {}
}
return p;
} else {
throw new IOException("Unknown element: " + type);
}
}
private static ArcType parseArcType(Node node) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("arc")) {
NamedNodeMap attr = node.getAttributes();
String arcType = parseString(attr, "type");
if (arcType == null || arcType.equalsIgnoreCase("open")) {
return ArcType.OPEN;
} else if (arcType.equalsIgnoreCase("chord")) {
return ArcType.CHORD;
} else if (arcType.equalsIgnoreCase("pie")) {
return ArcType.PIE;
} else {
return null;
}
} else {
throw new IOException("Unknown element: " + type);
}
}
private static ParameterizedValue[] parseValues(NamedNodeMap attr, String key) {
if (attr == null) return new ParameterizedValue[0];
Node node = attr.getNamedItem(key);
if (node == null) return new ParameterizedValue[0];
String text = node.getTextContent();
if (text == null) return new ParameterizedValue[0];
try {
List<ParameterizedValue> values = new ArrayList<ParameterizedValue>();
ExpressionLexer lexer = new ExpressionLexer(text);
ExpressionParser parser = new ExpressionParser(lexer);
while (lexer.hasNext()) {
int start = lexer.currentIndex();
Expression expr = parser.parseExpression();
int end = lexer.currentIndex();
String source = text.substring(start, end);
values.add(new ParameterizedValue(source, expr));
}
return values.toArray(new ParameterizedValue[values.size()]);
} catch (ExpressionParserException e) {
return new ParameterizedValue[0];
}
}
private static ParameterizedValue parseValue(NamedNodeMap attr, String key, double def) {
if (attr == null) return new ParameterizedValue(def);
Node node = attr.getNamedItem(key);
if (node == null) return new ParameterizedValue(def);
String text = node.getTextContent();
if (text == null) return new ParameterizedValue(def);
try {
ExpressionParser parser = new ExpressionParser(text);
Expression expr = parser.parse();
return new ParameterizedValue(text, expr);
} catch (ExpressionParserException e) {
return new ParameterizedValue(def);
}
}
private static WindingRule parseWindingRule(NamedNodeMap attr, String key, WindingRule def) {
if (attr == null) return def;
Node node = attr.getNamedItem(key);
if (node == null) return def;
String text = node.getTextContent();
if (text == null) return def;
if (
text.equalsIgnoreCase("eo") ||
text.equalsIgnoreCase("evenodd") ||
text.equalsIgnoreCase("even-odd")
) {
return WindingRule.EVEN_ODD;
}
if (
text.equalsIgnoreCase("nz") ||
text.equalsIgnoreCase("nonzero") ||
text.equalsIgnoreCase("non-zero")
) {
return WindingRule.NON_ZERO;
}
return def;
}
private static boolean parseBoolean(NamedNodeMap attr, String key, String trueValue, String falseValue, boolean def) {
if (attr == null) return def;
Node node = attr.getNamedItem(key);
if (node == null) return def;
String text = node.getTextContent();
if (text == null) return def;
text = text.trim();
if (text.equalsIgnoreCase(trueValue)) return true;
if (text.equalsIgnoreCase(falseValue)) return false;
return def;
}
private static double parseDouble(NamedNodeMap attr, String key, double def) {
if (attr == null) return def;
Node node = attr.getNamedItem(key);
if (node == null) return def;
String text = node.getTextContent();
if (text == null) return def;
try { return Double.parseDouble(text.trim()); }
catch (NumberFormatException nfe) { return def; }
}
private static String parseString(NamedNodeMap attr, String key) {
if (attr == null) return null;
Node node = attr.getNamedItem(key);
if (node == null) return null;
String text = node.getTextContent();
if (text == null) return null;
return text.trim();
}
private static List<Node> getChildren(Node node) {
List<Node> list = new ArrayList<Node>();
if (node != null) {
NodeList children = node.getChildNodes();
if (children != null) {
int count = children.getLength();
for (int i = 0; i < count; i++) {
Node child = children.item(i);
if (child != null) {
String type = child.getNodeName();
if (type.equalsIgnoreCase("#text") || type.equalsIgnoreCase("#comment")) {
continue;
} else {
list.add(child);
}
}
}
}
}
return list;
}
private static class SHPXEntityResolver implements EntityResolver {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (publicId.contains("PowerShape") || systemId.contains("shpx.dtd")) {
return new InputSource(PowerShapeParser.class.getResourceAsStream("shpx.dtd"));
} else {
return null;
}
}
}
private static class SHPXErrorHandler implements ErrorHandler {
private final String name;
public SHPXErrorHandler(String name) {
this.name = name;
}
@Override
public void error(SAXParseException e) throws SAXException {
System.err.print("Warning: Failed to compile shape set " + name + ": ");
System.err.println("ERROR on "+e.getLineNumber()+":"+e.getColumnNumber()+": "+e.getMessage());
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
System.err.print("Warning: Failed to compile shape set " + name + ": ");
System.err.println("FATAL ERROR on "+e.getLineNumber()+":"+e.getColumnNumber()+": "+e.getMessage());
}
@Override
public void warning(SAXParseException e) throws SAXException {
System.err.print("Warning: Failed to compile shape set " + name + ": ");
System.err.println("WARNING on "+e.getLineNumber()+":"+e.getColumnNumber()+": "+e.getMessage());
}
}
}