// // XMLTools.java // /* LOCI Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan, Eric Kjellman and Brian Loranger. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats; import java.io.*; import java.util.StringTokenizer; import javax.xml.parsers.*; import org.xml.sax.*; import org.xml.sax.helpers.DefaultHandler; /** * A utility class for working with XML (not necessarily OME-XML). * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/XMLTools.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/XMLTools.java">SVN</a></dd></dl> */ public final class XMLTools { // -- Constructor -- private XMLTools() { } // -- Utility methods -- /** * Attempts to validate the given XML string using * Java's XML validation facility. Requires Java 1.5+. * @param xml The XML string to validate. */ public static void validateXML(String xml) { validateXML(xml, null); } /** * Attempts to validate the given XML string using * Java's XML validation facility. Requires Java 1.5+. * @param xml The XML string to validate. * @param label String describing the type of XML being validated. */ public static void validateXML(String xml, String label) { if (label == null) label = "XML"; // check Java version (XML validation only works in Java 1.5+) String version = System.getProperty("java.version"); int dot = version.indexOf("."); if (dot >= 0) dot = version.indexOf(".", dot + 1); float ver = Float.NaN; if (dot >= 0) { try { ver = Float.parseFloat(version.substring(0, dot)); } catch (NumberFormatException exc) { } } if (ver != ver) { LogTools.println("Warning: cannot determine if Java version\"" + version + "\" supports Java v1.5. XML validation may fail."); } if (ver < 1.5f) return; // do not attempt validation if not Java 1.5+ // get path to schema from root element using SAX LogTools.println("Parsing schema path"); ValidationSAXHandler saxHandler = new ValidationSAXHandler(); SAXParserFactory saxFactory = SAXParserFactory.newInstance(); Exception exception = null; try { SAXParser saxParser = saxFactory.newSAXParser(); InputStream is = new ByteArrayInputStream(xml.getBytes()); saxParser.parse(is, saxHandler); } catch (ParserConfigurationException exc) { exception = exc; } catch (SAXException exc) { exception = exc; } catch (IOException exc) { exception = exc; } if (exception != null) { LogTools.println("Error parsing schema path from " + label + ":"); LogTools.trace(exception); return; } String schemaPath = saxHandler.getSchemaPath(); if (schemaPath == null) { LogTools.println("No schema path found. Validation cannot continue."); return; } else LogTools.println(schemaPath); LogTools.println("Validating " + label); // use reflection to avoid compile-time dependency on optional // org.openmicroscopy.xml or javax.xml.validation packages ReflectedUniverse r = new ReflectedUniverse(); try { // look up a factory for the W3C XML Schema language r.setVar("xmlSchemaPath", "http://www.w3.org/2001/XMLSchema"); r.exec("import javax.xml.validation.SchemaFactory"); r.exec("factory = SchemaFactory.newInstance(xmlSchemaPath)"); // compile the schema r.exec("import java.net.URL"); r.setVar("schemaPath", schemaPath); r.exec("schemaLocation = new URL(schemaPath)"); r.exec("schema = factory.newSchema(schemaLocation)"); // HACK - workaround for weird Linux bug preventing use of // schema.newValidator() method even though it is "public final" r.setAccessibilityIgnored(true); // get a validator from the schema r.exec("validator = schema.newValidator()"); // prepare the XML source r.exec("import java.io.StringReader"); r.setVar("xml", xml); r.exec("reader = new StringReader(xml)"); r.exec("import org.xml.sax.InputSource"); r.exec("is = new InputSource(reader)"); r.exec("import javax.xml.transform.sax.SAXSource"); r.exec("source = new SAXSource(is)"); // validate the XML ValidationErrorHandler errorHandler = new ValidationErrorHandler(); r.setVar("errorHandler", errorHandler); r.exec("validator.setErrorHandler(errorHandler)"); r.exec("validator.validate(source)"); if (errorHandler.ok()) LogTools.println("No validation errors found."); } catch (ReflectException exc) { LogTools.println("Error validating " + label + ":"); LogTools.trace(exc); } } /** Indents XML to be more readable. */ public static String indentXML(String xml) { return indentXML(xml, 3); } /** Indents XML by the given spacing to be more readable. */ public static String indentXML(String xml, int spacing) { int indent = 0; StringBuffer sb = new StringBuffer(); StringTokenizer st = new StringTokenizer(xml, "<>", true); boolean element = false; while (st.hasMoreTokens()) { String token = st.nextToken().trim(); if (token.equals("")) continue; if (token.equals("<")) { element = true; continue; } if (element && token.equals(">")) { element = false; continue; } if (element && token.startsWith("/")) indent -= spacing; for (int j=0; j<indent; j++) sb.append(" "); if (element) sb.append("<"); sb.append(token); if (element) sb.append(">"); sb.append("\n"); if (element && !token.startsWith("?") && !token.startsWith("/") && !token.endsWith("/")) { indent += spacing; } } return sb.toString(); } // -- Helper classes -- /** Used by validateXML to parse the XML block's schema path using SAX. */ private static class ValidationSAXHandler extends DefaultHandler { private String schemaPath; private boolean first; public String getSchemaPath() { return schemaPath; } public void startDocument() { schemaPath = null; first = true; } public void startElement(String uri, String localName, String qName, Attributes attributes) { if (!first) return; first = false; int len = attributes.getLength(); String xmlns = null, xsiSchemaLocation = null; for (int i=0; i<len; i++) { String name = attributes.getQName(i); if (name.equals("xmlns")) xmlns = attributes.getValue(i); else if (name.equals("xsi:schemaLocation")) { xsiSchemaLocation = attributes.getValue(i); } } if (xmlns == null || xsiSchemaLocation == null) return; // not found StringTokenizer st = new StringTokenizer(xsiSchemaLocation); while (st.hasMoreTokens()) { String token = st.nextToken(); if (xmlns.equals(token)) { // next token is the actual schema path if (st.hasMoreTokens()) schemaPath = st.nextToken(); break; } } } } /** Used by validateXML to handle XML validation errors. */ private static class ValidationErrorHandler implements ErrorHandler { private boolean ok = true; public boolean ok() { return ok; } public void error(SAXParseException e) { LogTools.println("error: " + e.getMessage()); ok = false; } public void fatalError(SAXParseException e) { LogTools.println("fatal error: " + e.getMessage()); ok = false; } public void warning(SAXParseException e) { LogTools.println("warning: " + e.getMessage()); ok = false; } } }