dk.hippogrif.prettyxml.PrettyPrint.java Source code

Java tutorial

Introduction

Here is the source code for dk.hippogrif.prettyxml.PrettyPrint.java

Source

/*
Copyright (C) 2005 Jesper Goertz
All Rights Reserved, http://hippogrif.dk/sw/prettyxml
     
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
     
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package dk.hippogrif.prettyxml;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;

import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import org.jdom.transform.XSLTransformer;
import org.jdom.transform.XSLTransformException;

/**
 * Prettyprints XML based on JDOM 1.0 according to a set of properties
 * specifying format and options.
 * See {@link dk.hippogrif.prettyxml}.
 *
 * @author Jesper Goertz
 */
public class PrettyPrint implements PropertyNames {
    private static Logger logger = Logger.getLogger(PrettyPrint.class.getName());
    private static String[] encodings;
    private static String[] settings;
    private static HashMap setting;
    private static String[] transformations;
    private static HashMap transformation;

    /**
     * Accepted properties (basic and extended).
     */
    static ArrayList keys = initKeys();

    private static String version = loadConfiguration("prettyxml.properties");

    private static ArrayList initKeys() {
        ArrayList keys = new ArrayList(Arrays.asList(BASIC_KEYS));
        if (keys == null)
            throw new RuntimeException("basic keys null");
        keys.addAll(Arrays.asList(EXTENDED_KEYS));
        if (keys == null)
            throw new RuntimeException("added keys null");
        return keys;
    }

    /**
     * Get prettyxml version.
     */
    public static String getVersion() {
        return version;
    }

    class MyLevel extends Level {
        public MyLevel(int level) {
            super("prettyxml", level);
        }
    }

    /**
     * Get wellknown encodings.
     **/
    public static String[] getEncodings() {
        return encodings;
    }

    /**
     * Get names of wellknown properties settings.
     **/
    public static String[] getSettings() {
        return settings;
    }

    /**
     * Get wellknown properties setting.
     **/
    public static Properties getSetting(String name) {
        return (Properties) setting.get(name);
    }

    /**
     * Get default properties setting - the first in configured settings.
     **/
    public static Properties getDefaultSetting() {
        return (Properties) setting.get(settings[0]);
    }

    /**
     * Get names of wellknown transformations.
     **/
    public static String[] getTransformations() {
        return transformations;
    }

    /**
     * Get wellknown transformation.
     **/
    public static XSLTransformer getTransformation(String name) {
        return (XSLTransformer) transformation.get(name);
    }

    static String loadConfiguration(String resource) {
        try {
            Properties prop = loadPropertiesResource(resource);

            // logging
            String loggingLevel = prop.getProperty("logging.Level");
            if (loggingLevel != null) {
                Level level = Level.parse(loggingLevel);
                Logger l = Logger.getLogger("dk.hippogrif.prettyxml");
                l.setLevel(level);
                if ("ConsoleHandler".equals(prop.getProperty("logging.Handler"))) {
                    ConsoleHandler h = new ConsoleHandler();
                    h.setLevel(level);
                    l.addHandler(h);
                } else if ("FileHandler".equals(prop.getProperty("logging.Handler"))) {
                    FileHandler h = new FileHandler(System.getProperty("user.home") + "/prettyxml.log");
                    h.setLevel(level);
                    l.addHandler(h);
                }
                logger.config("logging.Level=" + loggingLevel);
            }

            // version
            version = prop.getProperty("version", "");
            logger.config("version=" + version);

            // wellknown encodings
            String s = prop.getProperty("encodings");
            if (s == null) {
                throw new Exception("encodings missing in prettyxml.properties");
            }
            encodings = s.split(";");

            // wellknown property settings
            s = prop.getProperty("settings");
            if (s == null) {
                throw new Exception("settings missing in prettyxml.properties");
            }
            settings = s.split(";");
            setting = new HashMap();
            for (int i = 0; i < settings.length; i++) {
                String name = settings[i];
                Properties props = loadPropertiesResource(name + ".properties");
                checkProperties(props, false);
                setting.put(name, props);
            }

            // wellknown transformations
            s = prop.getProperty("transformations");
            if (s == null) {
                throw new Exception("transformations missing in prettyxml.properties");
            }
            transformations = s.split(";");
            transformation = new HashMap();
            for (int i = 0; i < transformations.length; i++) {
                String name = transformations[i];
                transformation.put(name, mkTransformerResource(name + ".xslt"));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return version;
    }

    static void setIndentation(String no, Format format) {
        int n = Integer.parseInt(no);
        if (n > 0) {
            StringBuffer sb = new StringBuffer(n);
            for (int i = 0; i < n; i++) {
                sb.append(' ');
            }
            format.setIndent(sb.toString());
        }
    }

    static Format initFormat(Properties prop) throws Exception {
        Format format = Format.getRawFormat();
        String p;
        if ((p = prop.getProperty(ENCODING)) != null) {
            format.setEncoding(p);
        }
        if ((p = prop.getProperty(EXPAND_EMPTY_ELEMENTS)) != null) {
            format.setExpandEmptyElements(Boolean.valueOf(p).booleanValue());
        }
        if ((p = prop.getProperty(INDENT)) != null) {
            setIndentation(p, format);
        }
        if ((p = prop.getProperty(LINE_SEPARATOR)) != null) {
            format.setLineSeparator(p);
        }
        if ((p = prop.getProperty(OMIT_DECLARATION)) != null) {
            format.setOmitDeclaration(Boolean.valueOf(p).booleanValue());
        }
        if ((p = prop.getProperty(OMIT_ENCODING)) != null) {
            format.setOmitEncoding(Boolean.valueOf(p).booleanValue());
        }
        if ((p = prop.getProperty(TEXT_MODE)) != null) {
            if (p.equals("NORMALIZE")) {
                format.setTextMode(Format.TextMode.NORMALIZE);
            } else if (p.equals("TRIM")) {
                format.setTextMode(Format.TextMode.TRIM);
            } else if (p.equals("TRIM_FULL_WHITE")) {
                format.setTextMode(Format.TextMode.TRIM_FULL_WHITE);
            } else if (!p.equals("PRESERVE")) {
                throw new Exception("invalid " + TEXT_MODE + " in properties file: " + p);
            }
        }
        return format;
    }

    /**
     * Checks known properties -
     * present boolean properties are set to either "true" or "false",
     * indent=0 is removed,
     * string properties are trimmed and empty properties removed -
     * however, TEXT_MODE is checked when format is initialized
     * and ENCODING when used.
     *
     * @param prop holds the properties
     * @param extended to include in/out properties
     * @throws Exception if property error
     */
    public static void checkProperties(Properties prop, boolean extended) throws Exception {
        for (Enumeration elements = prop.propertyNames(); elements.hasMoreElements();) {
            String s = (String) elements.nextElement();
            if (!keys.contains(s)) {
                prop.remove(s);
            }
        }
        checkBoolean(EXPAND_EMPTY_ELEMENTS, prop);
        checkBoolean(OMIT_DECLARATION, prop);
        checkBoolean(OMIT_ENCODING, prop);
        checkBoolean(INDENT_ATTRIBUTES, prop);
        checkBoolean(SORT_ATTRIBUTES, prop);
        if (prop.containsKey(INDENT)) {
            try {
                int i = Integer.parseInt(prop.getProperty(INDENT));
                if (i == 0) {
                    prop.remove(INDENT);
                } else if (i < 1 || i > 99) {
                    throw new Exception(INDENT + " must be an integer >= 0 and < 100");
                }
            } catch (NumberFormatException e) {
                throw new Exception(INDENT + " must be an integer >= 0 and < 100");
            }
        }
        if (prop.containsKey(LINE_SEPARATOR)) {
            String s = prop.getProperty(LINE_SEPARATOR);
            boolean err = true;
            for (int i = 0; i < LINE_SEPARATORS.length; i++) {
                if (LINE_SEPARATORS[i].equals(s)) {
                    err = false;
                    break;
                }
            }
            if (err) {
                throw new Exception(LINE_SEPARATOR + " must be \\r, \\n or \\r\\n");
            }
        }
        checkString(TRANSFORM, prop);
        if (extended) {
            checkString(INPUT, prop);
            checkString(URL, prop);
            checkString(OUTPUT, prop);
            if (prop.containsKey(INPUT) && prop.containsKey(URL)) {
                throw new Exception("do not use " + INPUT + " and " + URL + " at the same time");
            }
        } else {
            prop.remove(INPUT);
            prop.remove(URL);
            prop.remove(OUTPUT);
        }
    }

    /**
     * Load properties from file or as resource in classpath if file not found.
     *
     * @param name of properties file or resource
     * @throws IOException if io error on reading file
     * @throws Exception if resource not found
     */
    public static Properties loadProperties(String name) throws Exception {
        File file = new File(name);
        if (file.isFile()) {
            return loadProperties(file);
        }
        return loadPropertiesResource(name);
    }

    /**
     * Load properties as resource in classpath.
     *
     * @param name of resource holding properties
     * @throws Exception if resource not found
     */
    public static Properties loadPropertiesResource(String name) throws Exception {
        InputStream is = null;
        try {
            is = PrettyPrint.class.getResourceAsStream("/" + name);
            if (is == null) {
                throw new Exception("cannot find properties " + name);
            }
            Properties prop = new Properties();
            prop.load(is);
            return prop;
        } finally {
            IOUtils.closeQuietly(is);
        }
    }

    /**
     * Load properties from file.
     *
     * @param file holding properties
     * @throws IOException on io error
     */
    public static Properties loadProperties(File file) throws IOException {
        Properties prop = new Properties();
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            prop.load(fis);
        } finally {
            IOUtils.closeQuietly(fis);
        }
        return prop;
    }

    /**
     * Store properties in file with version in header.
     *
     * @param file to store the properties in
     * @param prop the properties to store
     * @throws IOException on io error
     */
    public static void storeProperties(File file, Properties prop) throws IOException {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            prop.store(fos, "prettyxml " + version);
        } finally {
            IOUtils.closeQuietly(fos);
        }
    }

    static void checkBoolean(String key, Properties prop) throws Exception {
        String s = prop.getProperty(key);
        if (s != null) {
            if ("false".equalsIgnoreCase(s)) {
                prop.setProperty(key, "false");
            } else if ("true".equalsIgnoreCase(s)) {
                prop.setProperty(key, "true");
            } else {
                throw new Exception("value of property " + key + " must be true or false, was: " + s);
            }
        }
    }

    static void checkString(String key, Properties prop) {
        String s = prop.getProperty(key);
        if (s != null) {
            s = s.trim();
            if (s.length() == 0) {
                prop.remove(key);
            } else {
                prop.setProperty(key, s);
            }
        }
    }

    /**
     * Construct an XSL transformer.
     *
     * @param name of stylesheet file or resource
     * @throws Exception if stylesheet not found
     */
    public static XSLTransformer mkTransformer(String name) throws Exception {
        if (transformation.containsKey(name)) {
            return (XSLTransformer) transformation.get(name);
        }
        File file = new File(name);
        if (file.isFile()) {
            return new XSLTransformer(file);
        }
        return mkTransformerResource(name);
    }

    private static XSLTransformer mkTransformerResource(String name) throws Exception {
        InputStream is = null;
        try {
            is = PrettyPrint.class.getResourceAsStream("/" + name);
            if (is == null) {
                throw new Exception("cannot find stylesheet " + name);
            }
            return new XSLTransformer(is);
        } finally {
            IOUtils.closeQuietly(is);
        }
    }

    /**
     * Do the prettyprint according to properties.
     *
     * @throws Exception if something goes wrong
     */
    public static void execute(Properties prop) throws Exception {
        execute(prop, null);
    }

    /**
     * Do the prettyprint according to properties.
     *
     * @param input holds xml document as text if present
     * @return prettyprinted document as text if input param present
     * @throws Exception if something goes wrong
     */
    public static String execute(Properties prop, String input) throws Exception {
        try {
            checkProperties(prop, true);
            logger.log(Level.FINEST, "input=" + input + " properties=" + prop);
            Format format = initFormat(prop);
            PrettyXMLOutputter outp = new PrettyXMLOutputter(format);
            outp.setSortAttributes(prop.containsKey(SORT_ATTRIBUTES));
            outp.setIndentAttributes(prop.containsKey(INDENT_ATTRIBUTES));
            SAXBuilder builder = new SAXBuilder();
            Document doc;
            if (input != null) {
                doc = builder.build(new StringReader(input));
            } else if (prop.containsKey(INPUT)) {
                doc = builder.build(new File(prop.getProperty(INPUT)));
            } else if (prop.containsKey(URL)) {
                doc = builder.build(new URL(prop.getProperty(URL)));
            } else {
                doc = builder.build(System.in);
            }
            if (prop.containsKey(TRANSFORM)) {
                String[] sa = prop.getProperty(TRANSFORM).split(";");
                for (int i = 0; i < sa.length; i++) {
                    XSLTransformer transformer = mkTransformer(sa[i].trim());
                    doc = transformer.transform(doc);
                }
            }
            if (prop.containsKey(OUTPUT)) {
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(new File(prop.getProperty(OUTPUT)));
                    outp.output(doc, fos);
                } finally {
                    IOUtils.closeQuietly(fos);
                }
            } else if (input != null) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                outp.output(doc, baos);
                return baos.toString(prop.getProperty(ENCODING, "UTF-8"));
            } else {
                outp.output(doc, System.out);
            }
            return null;
        } catch (Exception e) {
            logger.log(Level.FINER, "properties=" + prop, e);
            throw e;
        }
    }

}