package com.kreative.paint.material.alphabet;
import java.awt.Font;
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 AlphabetParser {
public static AlphabetList 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 ALPXEntityResolver());
builder.setErrorHandler(new ALPXErrorHandler(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 AlphabetList 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("alphabets")) {
if (child.hasAttributes() || child.hasChildNodes()) {
return parseAlphabets(child);
}
} else {
throw new IOException("Unknown element: " + ctype);
}
}
throw new IOException("Empty document.");
} else {
throw new IOException("Unknown element: " + type);
}
}
private static AlphabetList parseAlphabets(Node node) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("alphabets")) {
NamedNodeMap attr = node.getAttributes();
String name = parseString(attr, "name");
int width = parseInt(attr, "width", Alphabet.DEFAULT_WIDTH);
String fn = parseString(attr, "font-family");
if (fn == null || fn.equalsIgnoreCase("inherit")) fn = Alphabet.DEFAULT_FONT.getFamily();
int fs = parseInt(attr, "font-size", Alphabet.DEFAULT_FONT.getSize());
boolean fb = parseBoolean(attr, "font-weight", "bold", "normal", Alphabet.DEFAULT_FONT.isBold());
boolean fi = parseBoolean(attr, "font-style", "italic", "normal", Alphabet.DEFAULT_FONT.isItalic());
Font font = new Font(fn, ((fb ? Font.BOLD : 0) | (fi ? Font.ITALIC : 0)), fs);
AlphabetList list = new AlphabetList(name, width, font);
for (Node child : getChildren(node)) {
list.add(parseAlphabet(child, list));
}
return list;
} else {
throw new IOException("Unknown element: " + type);
}
}
private static Alphabet parseAlphabet(Node node, AlphabetList parent) throws IOException {
String type = node.getNodeName();
if (type.equalsIgnoreCase("alphabet")) {
NamedNodeMap attr = node.getAttributes();
String name = parseString(attr, "name");
int width = parseInt(attr, "width", parent.width);
String fn = parseString(attr, "font-family");
if (fn == null || fn.equalsIgnoreCase("inherit")) fn = parent.font.getFamily();
int fs = parseInt(attr, "font-size", parent.font.getSize());
boolean fb = parseBoolean(attr, "font-weight", "bold", "normal", parent.font.isBold());
boolean fi = parseBoolean(attr, "font-style", "italic", "normal", parent.font.isItalic());
Font font = new Font(fn, ((fb ? Font.BOLD : 0) | (fi ? Font.ITALIC : 0)), fs);
String text = node.getTextContent();
List<Integer> letters = new ArrayList<Integer>();
if (text != null) {
int i = 0, n = text.length();
while (i < n) {
int ch = text.codePointAt(i);
if (isPrintable(ch)) letters.add(ch);
i += Character.charCount(ch);
}
}
return new Alphabet(name, width, font, letters);
} else {
throw new IOException("Unknown element: " + type);
}
}
private static boolean isPrintable(int ch) {
// Low end filter.
if (ch < 0x21) return false;
if (ch < 0x7F) return true;
if (ch < 0xA1) return false;
// High end filter.
if (ch >= 0x10FFFE) return false;
if ((ch & 0xFFFF) >= 0xFFFE) return false;
if (ch == 0xFFEF) return false;
// The rest.
if (Character.isWhitespace(ch)) return false;
if (Character.isSpaceChar(ch)) return false;
return true;
}
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 int parseInt(NamedNodeMap attr, String key, int 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 Integer.parseInt(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 ALPXEntityResolver implements EntityResolver {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (publicId.contains("PowerAlphabet") || systemId.contains("alpx.dtd")) {
return new InputSource(AlphabetParser.class.getResourceAsStream("alpx.dtd"));
} else {
return null;
}
}
}
private static class ALPXErrorHandler implements ErrorHandler {
private final String name;
public ALPXErrorHandler(String name) {
this.name = name;
}
@Override
public void error(SAXParseException e) throws SAXException {
System.err.print("Warning: Failed to compile alphabet 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 alphabet 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 alphabet set " + name + ": ");
System.err.println("WARNING on "+e.getLineNumber()+":"+e.getColumnNumber()+": "+e.getMessage());
}
}
}