/** * Copyright (C) 2005 - 2013 Eric Van Dewoestine * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.core.command.xml; import java.io.FileInputStream; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Method; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.eclim.annotation.Command; import org.eclim.command.CommandLine; import org.eclim.command.Options; import org.eclim.plugin.core.command.AbstractCommand; import org.eclim.util.IOUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSSerializer; import org.xml.sax.InputSource; /** * Command to format an xml file. * * @author Eric Van Dewoestine */ @Command( name = "xml_format", options = "REQUIRED f file ARG," + "REQUIRED w linewidth ARG," + "REQUIRED i indent ARG," + "REQUIRED m fileformat ARG" ) public class FormatCommand extends AbstractCommand { @Override public Object execute(CommandLine commandLine) throws Exception { FileInputStream in = null; try { String file = commandLine.getValue(Options.FILE_OPTION); String format = commandLine.getValue("m"); int lineWidth = commandLine.getIntValue(Options.LINE_WIDTH_OPTION); int indent = commandLine.getIntValue(Options.INDENT_OPTION); in = new FileInputStream(file); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // disable validation since we only want to reformat. factory.setValidating(false); factory.setValidating(false); factory.setFeature("http://xml.org/sax/features/namespaces", false); factory.setFeature("http://xml.org/sax/features/validation", false); factory.setFeature( "http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); factory.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new InputSource(in)); DOMImplementationRegistry registry = DOMImplementationRegistry .newInstance(); DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS"); LSSerializer serializer = impl.createLSSerializer(); serializer.getDomConfig() .setParameter("format-pretty-print", Boolean.TRUE); if (format.equals("unix")) { serializer.setNewLine("\n"); } else if (format.equals("dos")) { serializer.setNewLine("\r\n"); } // attempt to serialize with the requested indent + line width try{ return serialize(document, serializer, indent, lineWidth); }catch(Exception e){ // if the reflection hack fails, then just format with the default // indent + line width return serializer.writeToString(document); } } finally { IOUtils.closeQuietly(in); } } // very gross reflection, all to set the indent + line width private String serialize( Document document, LSSerializer serializer, int indent, int lineWidth) throws Exception { Object ser = getField(serializer, "serializer"); invokeMethod(serializer, "prepareForSerialization", new Class[]{ser.getClass(), Node.class}, new Object[]{ser, document}); StringWriter out = new StringWriter(); invokeMethod(ser, "setOutputCharStream", new Class[]{Writer.class}, new Object[]{out}); invokeMethod(ser, "reset", new Class[0], new Object[0]); invokeMethod(ser, "prepare", new Class[0], new Object[0]); Object printer = getField(ser, "_printer"); // set the indent and line width Object format = getField(printer, "_format"); invokeMethod(format, "setIndent", new Class[]{Integer.TYPE}, new Object[]{indent}); invokeMethod(format, "setLineWidth", new Class[]{Integer.TYPE}, new Object[]{lineWidth}); // perform the serialization (from BaseMarkupSerializer.serialize(Document)) invokeMethod(ser, "serializeNode", new Class[]{Node.class}, new Object[]{document}); invokeMethod(ser, "serializePreRoot", new Class[0], new Object[0]); invokeMethod(printer, "flush", new Class[0], new Object[0]); Exception ex = (Exception)getField(printer, "_exception"); if (ex != null){ throw ex; } return out.toString(); } @SuppressWarnings("rawtypes") private Object getField(Object obj, String name) throws Exception { Class clazz = obj.getClass(); Exception ex = null; while (clazz != null){ try{ Field field = clazz.getDeclaredField(name); field.setAccessible(true); return field.get(obj); }catch(NoSuchFieldException nsfe){ ex = nsfe; clazz = clazz.getSuperclass(); } } throw ex; } @SuppressWarnings({ "rawtypes", "unchecked" }) private void invokeMethod( Object obj, String name, Class[] params, Object[] args) throws Exception { Class clazz = obj.getClass(); Exception ex = null; while (clazz != null){ try{ Method method = clazz.getDeclaredMethod(name, params); method.setAccessible(true); method.invoke(obj, args); return; }catch(NoSuchMethodException nsme){ ex = nsme; clazz = clazz.getSuperclass(); } } throw ex; } }