package org.madore.damlengine; import java.util.MissingResourceException; import java.util.regex.Pattern; import java.util.regex.Matcher; import java.util.Properties; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.BufferedReader; import java.io.PrintStream; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.nio.file.NoSuchFileException; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import org.w3c.dom.*; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSParser; import org.w3c.dom.ls.LSInput; import org.apache.xerces.dom.DOMImplementationSourceImpl; import org.apache.xerces.xni.parser.XMLErrorHandler; import org.apache.xerces.xni.parser.XMLParseException; public final class DamlEngine { // NOTE TO SELF: install the w3c-dtd-xhtml Debian package to have // a local copy of the various standard DTDs of HTML and not // download them from the W3C's web site. public static final String XMLNS_NS = XMLConstants.XMLNS_ATTRIBUTE_NS_URI; public static final String XML_NS = XMLConstants.XML_NS_URI; public static final String XHTML_NS = "http://www.w3.org/1999/xhtml"; public static final String DAML_NS = "http://www.madore.org/~david/NS/daml/"; public static final String RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; public static final String RSS10_NS = "http://purl.org/rss/1.0/"; public static final String DUBLINCORE_NS = "http://purl.org/dc/elements/1.1/"; public static final String SYNDICATION_NS = "http://purl.org/rss/1.0/modules/syndication/"; public static final class DamlNSMapping implements NamespaceContext { // This is used for XPath resolution (_not_ for parsing the document). public String getNamespaceURI(String prefix) { if ( prefix == null ) throw new IllegalArgumentException("getNamespaceURI() called with null prefix"); else if ( prefix.equals("") ) return XHTML_NS; else if ( prefix.equals("d") ) return DAML_NS; else if ( prefix.equals("xml") ) return XML_NS; else if ( prefix.equals("xmlns") ) return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; else return XMLConstants.NULL_NS_URI; } public String getPrefix(String uri) { throw new UnsupportedOperationException("getPrefix() not implemented"); } public java.util.Iterator getPrefixes(String uri) { throw new UnsupportedOperationException("getPrefixes() not implemented"); } } public static final class SelectiveErrorHandler implements XMLErrorHandler { public void warning(String domain, String key, XMLParseException exc) { System.err.println("warning: line "+exc.getLineNumber() +": "+exc.getMessage()); } public void error(String domain, String key, XMLParseException exc) { if ( domain.equals("http://www.w3.org/TR/1998/REC-xml-19980210") && key.equals("MSG_ELEMENT_NOT_DECLARED") ) return; System.err.println("error: line "+exc.getLineNumber() +": "+exc.getMessage()); } public void fatalError(String domain, String key, XMLParseException exc) { System.err.println("fatal error: line "+exc.getLineNumber() +": "+exc.getMessage()); throw exc; } } public static final class IncantDOM { static ThreadLocal pdomi = new ThreadLocal(); public static DOMImplementation getDOMI() { DOMImplementation domi = pdomi.get(); if ( domi == null ) { DOMImplementationSource source = new DOMImplementationSourceImpl(); domi = source.getDOMImplementation("XML 3.0 Core 3.0 LS 3.0"); if ( domi == null ) throw new MissingResourceException("failed to obtain DOM implementation", "org.w3c.dom.ls.DOMImplementationLS", ""); pdomi.set(domi); } return domi; } } private DamlEngine() { // Forbid instantiation throw new AssertionError("DamlEngine cannot be instantiated"); } public static class RootTodo extends TodoItem { public RootTodo(Context ctx) { super(ctx, null); } public void handle() { TodoItem it = TodoElement.getTodoElement(ctx.doc.getDocumentElement(), ctx, this); this.ownerDeque.registerAtEnd(it); } } public static void processDocument(Document doc, Context.WeblogSelectionContext wsc, Context.DynamicContext dc) { TodoDeque todoDeque = new TodoDeque(); Context ctx = new Context(doc); ctx.wsc = wsc; ctx.dc = dc; todoDeque.registerAtEnd(new RootTodo(ctx)); todoDeque.dispatchLoop(); } public static void processDocument(Document doc, Context.WeblogSelectionContext wsc) { processDocument(doc, wsc, null); } public static void processDocument(Document doc) { processDocument(doc, null, null); } public static void fullProcess(InputStream in, OutputStream out, Context.WeblogSelectionContext wsc, Context.DynamicContext dc) throws Exception { final DOMImplementationLS domils = (DOMImplementationLS)(IncantDOM.getDOMI()); LSParser par = domils.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null); par.getDomConfig().setParameter("resource-resolver", new Resolver()); par.getDomConfig().setParameter("http://xml.org/sax/features/validation", true); par.getDomConfig().setParameter("http://xml.org/sax/features/namespaces", true); par.getDomConfig().setParameter("http://apache.org/xml/properties/internal/error-handler", new SelectiveErrorHandler()); LSInput input = domils.createLSInput(); input.setByteStream(in); Document doc = par.parse(input); processDocument(doc, wsc, dc); doc.normalizeDocument(); Unparser unparser = new Unparser(doc, new OutputStreamWriter(out, "UTF-8"), "html"); unparser.unparse(); } public static void fullProcess(InputStream in, OutputStream out, Context.WeblogSelectionContext wsc) throws Exception { fullProcess(in, out, wsc, null); } public static void fullProcess(InputStream in, OutputStream out) throws Exception { fullProcess(in, out, null, null); } public static boolean runAsServlet = false; public static Properties appProps; public static Path basePath; public static Path templatePath; private interface Processor { public void process(OutputStream out) throws Exception; } private static void processWithTemp(Processor p, String outf) throws Exception { Path tempPath = (outf != null) ? Paths.get(outf+".temp") : null; OutputStream out = (tempPath != null) ? Files.newOutputStream(tempPath) : System.out; try { p.process(out); if ( tempPath != null ) { out.close(); Files.move(tempPath, Paths.get(outf), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } } finally { try { if (tempPath != null ) Files.deleteIfExists(tempPath); } catch ( Exception e ) { } } } public static void main(String[] args) throws Exception { appProps = new Properties(); Path appPropsFile = null; if ( System.getenv("DAMLENGINE_PROPERTIES_PATH") != null ) appPropsFile = Paths.get(System.getenv("DAMLENGINE_PROPERTIES_PATH")); if ( appPropsFile == null ) { if ( System.getProperty("user.home") != null ) appPropsFile = Paths.get(System.getProperty("user.home"),"damlengine.properties"); else appPropsFile = Paths.get("damlengine.properties"); } try { appProps.load(Files.newInputStream(appPropsFile)); } catch (NoSuchFileException e) { } basePath = null; if ( System.getenv("DAMLENGINE_BASE_PATH") != null ) basePath = Paths.get(System.getenv("DAMLENGINE_BASE_PATH")); if ( basePath == null && appProps.getProperty("base_path") != null ) basePath = Paths.get(appProps.getProperty("base_path")); if ( basePath == null ) { basePath = Paths.get("."); System.err.println("warning: using working directory as base path"); } templatePath = null; if ( System.getenv("DAMLENGINE_TEMPLATE_PATH") != null ) templatePath = Paths.get(System.getenv("DAMLENGINE_TEMPLATE_PATH")); if ( templatePath == null && appProps.getProperty("template_path") != null ) templatePath = Paths.get(appProps.getProperty("template_path")); if ( templatePath == null ) { templatePath = basePath.resolve("templates"); System.err.println("warning: using "+templatePath.toString()+" as template path"); } BufferedReader buf = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); String line; Matcher matcher; while ( ( line = buf.readLine() ) != null ) { if ( Pattern.matches("^\\s+$", line) ) continue; System.err.println(line); if ( (matcher=Pattern.compile("process\\s+(\\S+)(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final String inf = matcher.group(1); final String outf = matcher.group(2); final InputStream in = Files.newInputStream(basePath.resolve(inf)); processWithTemp(new Processor() { public void process(OutputStream out) throws Exception { fullProcess(in, out); } }, outf); } else if ( (matcher=Pattern.compile("process-weblog-month\\s+(\\d{4})\\-(\\d{2})(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final String year = matcher.group(1); final String month = matcher.group(2); final String outf = matcher.group(3); processWithTemp(new Processor() { public void process(OutputStream out) throws Exception { WeblogSelect.fullProcess(new Context.WeblogMonthSelectionContext(year,month), out); } }, outf); } else if ( (matcher=Pattern.compile("process-weblog-cat\\s+([a-z0-9\\-]+)(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final String code = matcher.group(1); final String outf = matcher.group(2); processWithTemp(new Processor() { public void process(OutputStream out) throws Exception { WeblogSelect.fullProcess(new Context.WeblogCategorySelectionContext(code), out); } }, outf); } else if ( (matcher=Pattern.compile("process-weblog-recent\\s+(\\d+)(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final int count = Integer.parseInt(matcher.group(1)); final String outf = matcher.group(2); processWithTemp(new Processor() { public void process(OutputStream out) throws Exception { WeblogSelect.fullProcess(new Context.WeblogRecentSelectionContext(count), out); } }, outf); } else if ( (matcher=Pattern.compile("process-weblog-single\\s+(\\d+)(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final int number = Integer.parseInt(matcher.group(1)); final String outf = matcher.group(2); processWithTemp(new Processor() { public void process(OutputStream out) throws Exception { WeblogSelect.fullProcess(new Context.WeblogSingleSelectionContext(number), out); } }, outf); } else if ( (matcher=Pattern.compile("process-weblog-index(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final String outf = matcher.group(1); processWithTemp(new Processor() { public void process(OutputStream out) throws Exception { WeblogIndexSelect.fullProcess(out); } }, outf); } else if ( (matcher=Pattern.compile("process-weblog-rss(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final String outf = matcher.group(1); processWithTemp(new Processor() { public void process(OutputStream out) throws Exception { WeblogRSS.fullProcess(out); } }, outf); } else if ( (matcher=Pattern.compile("populate-weblog\\s+(\\S+)\\s*").matcher(line)).matches() ) { final String inf = matcher.group(1); final InputStream in = Files.newInputStream(basePath.resolve(inf)); WeblogPopulate.populate(in); } else if ( (matcher=Pattern.compile("echo\\s+(\\S+)(?:\\s+\\>\\s*(\\S+))?\\s*").matcher(line)).matches() ) { final String str = matcher.group(1); final String outf = matcher.group(2); final OutputStream out = (outf != null) ? Files.newOutputStream(Paths.get(outf)) : System.out; new PrintStream(out, true).println(str); if ( outf != null ) out.close(); } else { throw new IllegalArgumentException("couldn't understand command"); } } } }