| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.freemarker.docgen; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Iterator; |
| |
| import javax.xml.XMLConstants; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.parsers.SAXParserFactory; |
| import javax.xml.validation.Schema; |
| import javax.xml.validation.SchemaFactory; |
| |
| import org.freemarker.docgen.ValidatingDOMBuilderWithLocations.Location; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| final class XMLUtil { |
| |
| // Can't be instantiated |
| private XMLUtil() { |
| // Nop |
| } |
| |
| private static Boolean cachedIsJingAvilable; |
| |
| static boolean isJingAvilable() { |
| if (cachedIsJingAvilable == null) { |
| ClassLoader cl = Transform.class.getClassLoader(); |
| cachedIsJingAvilable = Boolean.TRUE; |
| try { |
| cl.loadClass("com.thaiopensource.validate.Schema"); |
| } catch (ClassNotFoundException e) { |
| cachedIsJingAvilable = Boolean.FALSE; |
| } |
| } |
| return cachedIsJingAvilable.booleanValue(); |
| } |
| |
| static SAXParserFactory newSAXParserFactory() |
| throws SAXException { |
| SAXParserFactory spf = SAXParserFactory.newInstance(); |
| spf.setNamespaceAware(true); |
| try { |
| spf.setXIncludeAware(true); |
| } catch (UnsupportedOperationException e) { |
| throw new SAXException( |
| "The default SAX parser (XML) implementation doesn't " |
| + "support XInclude. Updating your Java installation will " |
| + "possibly fix this."); |
| } |
| spf.setValidating(false); // since we attach a schema explicitly |
| return spf; |
| } |
| |
| static DocumentBuilderFactory newDocumentBuilderFactory() |
| throws SAXException { |
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); |
| dbf.setNamespaceAware(true); |
| try { |
| dbf.setXIncludeAware(true); |
| } catch (UnsupportedOperationException e) { |
| throw new SAXException( |
| "The default Document builder (XML) implementation doesn't " |
| + "support XInclude. Updating your Java installation will " |
| + "possibly fix this."); |
| } |
| dbf.setValidating(false); // since we attach a schema explicitly |
| return dbf; |
| } |
| |
| static Document loadDocBook5XML(File bookFile, boolean validate, |
| DocgenValidationOptions validationOps, DocgenLogger logger) |
| throws SAXException, IOException, DocgenException { |
| logger.info("Loading " + bookFile.getAbsolutePath() + "..."); |
| if (validate) { |
| if (!isJingAvilable()) { |
| throw new DocgenException("Jing classes are reqired for the " |
| + "validation but couldn't be found."); |
| } |
| |
| // Reflection is used to prevent static linking to Jing. |
| Method vm; |
| try { |
| vm = Transform.class.getClassLoader().loadClass( |
| "org.freemarker.docgen.RelaxNGValidator") |
| .getMethod("load", new Class[] { |
| File.class, |
| DocgenValidationOptions.class}); |
| } catch (Throwable e) { |
| throw new BugException( |
| "Failed to get the " |
| + "org.freemarker.docgen.RelaxNGValidator.validate " |
| + "method (see cause exception).", |
| e); |
| } |
| try { |
| return (Document) vm.invoke(null, bookFile, validationOps); |
| } catch (InvocationTargetException e) { |
| Throwable te = e.getTargetException(); |
| if (te instanceof SAXException) { |
| throw (SAXException) te; |
| } |
| if (te instanceof IOException) { |
| throw (IOException) te; |
| } |
| throw new BugException( |
| "Failed to setup Relax NG validation " |
| + "(see cause exception).", e); |
| } catch (Throwable e) { |
| throw new BugException( |
| "Failed to invoke docgen.RelaxNGValidator method " |
| + "(see cause exception).", e); |
| } |
| } else { |
| logger.info("Validation disabled. Be sure the source is " |
| + "valid Docgen-restricted DocBook 5."); |
| } |
| |
| // Here we will use JAXP DocumentBuilderFactory and W3C XML Schema |
| ErrorHandler eh = new DraconianErrorHandler(logger); |
| |
| DocumentBuilderFactory dbf = newDocumentBuilderFactory(); |
| |
| SchemaFactory schemaFact = SchemaFactory.newInstance( |
| XMLConstants.W3C_XML_SCHEMA_NS_URI); |
| schemaFact.setErrorHandler(eh); |
| Schema schema = schemaFact.newSchema( |
| Transform.class.getResource("schema/docbook.xsd")); |
| if (validate) { |
| dbf.setSchema(schema); |
| } |
| DocumentBuilder db; |
| try { |
| db = dbf.newDocumentBuilder(); |
| } catch (ParserConfigurationException e) { |
| throw new BugException(e); |
| } |
| db.setErrorHandler(eh); |
| |
| return db.parse(bookFile); |
| } |
| |
| private static final class DraconianErrorHandler implements ErrorHandler { |
| |
| private final DocgenLogger logger; |
| |
| private DraconianErrorHandler(DocgenLogger logger) { |
| this.logger = logger; |
| } |
| |
| public void warning(SAXParseException spe) { |
| logger.info("- Warning: " + spe); |
| } |
| |
| public void error(SAXParseException spe) throws SAXParseException { |
| throw spe; |
| } |
| |
| public void fatalError(SAXParseException spe) throws SAXParseException { |
| throw spe; |
| } |
| |
| } |
| |
| public static String getAttribute(Element elem, String att) { |
| String res = elem.getAttribute(att); |
| return res.length() == 0 && !elem.hasAttribute(att) ? null : res; |
| } |
| |
| public static String getAttributeNS(Element elem, String namespace, String att) { |
| String res = elem.getAttributeNS(namespace, att); |
| return res.length() == 0 && !elem.hasAttributeNS(namespace, att) ? null : res; |
| } |
| |
| public static Iterable<Element> childrenElementsOf(final Node parent) { |
| return new Iterable<Element>() { |
| |
| public Iterator<Element> iterator() { |
| return new ElementIterator(); |
| } |
| |
| class ElementIterator implements Iterator<Element> { |
| private Node continueFrom; |
| private Element curNode; |
| |
| ElementIterator() { |
| continueFrom = parent.getFirstChild(); |
| fetch(); |
| } |
| |
| public boolean hasNext() { |
| return curNode != null; |
| } |
| |
| public Element next() { |
| Element res = curNode; |
| fetch(); |
| return res; |
| } |
| |
| public void remove() { |
| throw new UnsupportedOperationException("remove"); |
| } |
| |
| private void fetch() { |
| while (!(continueFrom == null |
| || continueFrom instanceof Element)) { |
| continueFrom = continueFrom.getNextSibling(); |
| } |
| if (continueFrom != null) { |
| curNode = (Element) continueFrom; |
| continueFrom = curNode.getNextSibling(); |
| } else { |
| curNode = null; |
| } |
| } |
| |
| } |
| |
| }; |
| } |
| |
| public static String theSomethingElement(Element elem) { |
| return theSomethingElement(elem, false); |
| } |
| |
| public static String theSomethingElement(Element elem, boolean capFirst) { |
| String id = getAttribute(elem, "id"); |
| if (id == null) { |
| id = getAttribute(elem, "xml:id"); |
| } |
| if (id != null && (id.startsWith(Transform.AUTO_ID_PREFIX) |
| || id.startsWith(Transform.DOCGEN_ID_PREFIX))) { |
| id = null; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| if (id != null || elem.getParentNode() instanceof Document) { |
| sb.append("the \""); |
| } else { |
| sb.append("a(n) \""); |
| } |
| sb.append(elem.getLocalName()); |
| sb.append("\" element"); |
| |
| if (id != null) { |
| sb.append(" with xml:id=\"").append(id).append("\""); |
| } |
| |
| Location loc = (Location) elem.getUserData( |
| ValidatingDOMBuilderWithLocations.KEY_LOCATION); |
| if (loc != null) { |
| sb.append(" (location: "); |
| boolean empty = true; |
| |
| String sysId = loc.getSystemId(); |
| if (sysId != null) { |
| // Since it goes into the middle of other error messages, |
| // keep only the file name and the containing directory name: |
| int slashIdx = sysId.lastIndexOf("/"); |
| if (slashIdx != -1) { |
| slashIdx = sysId.lastIndexOf("/", slashIdx - 1); |
| if (slashIdx > 0) { |
| sysId = "[...]" + sysId.substring(slashIdx); |
| } |
| } |
| sb.append(sysId); |
| empty = false; |
| } |
| if (loc.getLine() > 0) { |
| if (!empty) { |
| sb.append(':'); |
| } |
| sb.append(loc.getLine()); |
| empty = false; |
| } |
| if (loc.getColumn() > 0) { |
| if (!empty) { |
| sb.append(':'); |
| } |
| sb.append(loc.getColumn()); |
| } |
| sb.append(")"); |
| } |
| |
| if (capFirst) { |
| sb.setCharAt(0, Character.toUpperCase(sb.charAt(0))); |
| } |
| |
| return sb.toString(); |
| } |
| |
| } |