/* * Copyright 2003-2011 JetBrains s.r.o. * * Licensed 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 jetbrains.mps.util; import org.apache.log4j.Logger; import org.apache.log4j.LogManager; import jetbrains.mps.vfs.IFile; import org.jdom.Document; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.persistence.MultiStreamDataSource; import org.jetbrains.mps.openapi.persistence.StreamDataSource; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.*; public class JDOMUtil { private static final Logger LOG = LogManager.getLogger(JDOMUtil.class); private static SAXParserFactory factory = null; public static SAXParser createSAXParser() throws SAXException, ParserConfigurationException { if (factory == null) { factory = SAXParserFactory.newInstance(); } return factory.newSAXParser(); } public static Document loadDocument(IFile file) throws JDOMException, IOException { SAXBuilder saxBuilder = createBuilder(); InputStream in = null; try { in = file.openInputStream(); return saxBuilder.build(new InputStreamReader(in, FileUtil.DEFAULT_CHARSET)); } catch (JDOMException e) { LOG.error("FAILED TO LOAD FILE : " + file.getPath(), e); throw e; } catch (IOException e) { LOG.error("FAILED TO LOAD FILE : " + file.getPath(), e); throw e; } finally { if (in != null) { try { in.close(); } catch (IOException e) { LOG.error(null, e); } } } } public static Document loadDocument(InputSource source) throws JDOMException, IOException { SAXBuilder saxBuilder = createBuilder(); try { return saxBuilder.build(source); } catch (JDOMException e) { LOG.error("FAILED TO LOAD FILE : " + source.toString()); throw e; } catch (IOException e) { LOG.error("FAILED TO LOAD FILE : " + source.toString()); throw e; } } public static Document loadDocument(File file) throws JDOMException, IOException { SAXBuilder saxBuilder = createBuilder(); FileInputStream in = new FileInputStream(file); try { return saxBuilder.build(new InputStreamReader(in, FileUtil.DEFAULT_CHARSET)); } catch (JDOMException e) { LOG.error("FAILED TO LOAD FILE : " + file.getAbsolutePath()); throw e; } catch (IOException e) { LOG.error("FAILED TO LOAD FILE : " + file.getAbsolutePath()); throw e; } finally { in.close(); } } public static Document loadDocument(InputStream stream) throws JDOMException, IOException { SAXBuilder saxBuilder = createBuilder(); return saxBuilder.build(new InputStreamReader(stream, FileUtil.DEFAULT_CHARSET)); } public static Document loadDocument(Reader reader) throws IOException, JDOMException { SAXBuilder saxBuilder = createBuilder(); return saxBuilder.build(reader); } public static String asString(Document doc) { StringWriter writer = new StringWriter(); try { writeDocument(doc, writer); } catch (IOException e) { // This is hardly possible LOG.error(null, e); } return writer.toString(); } public static void writeDocument(Document document, String filePath) throws IOException { OutputStream stream = new BufferedOutputStream(new FileOutputStream(filePath)); try { writeDocument(document, stream); } finally { stream.close(); } } public static SAXBuilder createBuilder() { final SAXBuilder saxBuilder = new SAXBuilder(); saxBuilder.setEntityResolver(new EntityResolver() { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return new InputSource(new CharArrayReader(new char[0])); } }); return saxBuilder; } public static void writeDocument(Document document, IFile file) throws IOException { OutputStream stream = new BufferedOutputStream(file.openOutputStream()); try { writeDocument(document, stream); } finally { stream.close(); } } public static void writeDocument(Document document, StreamDataSource source) throws IOException { OutputStream stream = new BufferedOutputStream(source.openOutputStream()); try { writeDocument(document, stream); } finally { stream.close(); } } public static void writeDocument(Document document, MultiStreamDataSource source, String streamName) throws IOException { OutputStream stream = new BufferedOutputStream(source.openOutputStream(streamName)); try { writeDocument(document, stream); } finally { stream.close(); } } public static void writeDocument(Document document, File file) throws IOException { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if (!file.exists()) { file.createNewFile(); } OutputStream stream = new BufferedOutputStream(new FileOutputStream(file)); try { writeDocument(document, stream); } finally { stream.close(); } } public static void writeDocument(Document document, OutputStream stream) throws IOException { writeDocument(document, new OutputStreamWriter(stream, FileUtil.DEFAULT_CHARSET)); } public static byte[] printDocument(Document document) throws IOException { CharArrayWriter writer = new CharArrayWriter(); writeDocument(document, writer); return new String(writer.toCharArray()).getBytes(FileUtil.DEFAULT_CHARSET); } public static void writeDocument(Document document, Writer writer) throws IOException { XMLOutputter xmlOutputter = createOutputter(); if (xmlOutputter == null) { LOG.error("Could not create XMLOutputter"); } else if (document == null) { LOG.error("Document is null"); } else if (writer == null) { LOG.error("Writer is null"); return; } else { xmlOutputter.output(document, writer); } writer.close(); } public static XMLOutputter createOutputter() { XMLOutputter xmlOutputter = new MyXMLOutputter(); xmlOutputter.setFormat(Format.getPrettyFormat().setLineSeparator(System.getProperty("line.separator"))); return xmlOutputter; } public static class MyXMLOutputter extends XMLOutputter { @Override public String escapeAttributeEntities(String str) { return escapeText(str, false, true); } @Override public String escapeElementEntities(String str) { return escapeText(str, false, false); } } @NotNull public static String escapeText(String text, boolean escapeSpaces, boolean escapeLineEnds) { return escapeText(text, false, escapeSpaces, escapeLineEnds); } @NotNull public static String escapeText(String text, boolean escapeApostrophes, boolean escapeSpaces, boolean escapeLineEnds) { StringBuilder buffer = null; for (int i = 0; i < text.length(); i++) { final char ch = text.charAt(i); final String quotation = escapeChar(ch, escapeApostrophes, escapeSpaces, escapeLineEnds); if (buffer == null) { if (quotation != null) { // An quotation occurred, so we'll have to use StringBuilder // (allocate room for it plus a few more entities). buffer = new StringBuilder(text.length() + 20); // Copy previous skipped characters and fall through // to pickup current character buffer.append(text.substring(0, i)); buffer.append(quotation); } } else { if (quotation == null) { buffer.append(ch); } else { buffer.append(quotation); } } } // If there were any entities, return the escaped characters // that we put in the StringBuilder. Otherwise, just return // the unmodified input string. return buffer == null ? text : buffer.toString(); } /** * Returns null if no escapement necessary. */ @Nullable private static String escapeChar(char c, boolean escapeApostrophes, boolean escapeSpaces, boolean escapeLineEnds) { switch (c) { case '\n': return escapeLineEnds ? " " : null; case '\r': return escapeLineEnds ? " " : null; case '\t': return escapeLineEnds ? " " : null; case ' ': return escapeSpaces ? "" : null; case '<': return "<"; case '>': return ">"; case '\"': return """; case '\'': return escapeApostrophes ? "'" : null; case '&': return "&"; } return null; } public static String unescapeText(@NotNull String text) { StringBuilder buffer = null; for (int i = 0; i < text.length(); i++) { final char ch = text.charAt(i); String quotation = null; int start = i; if (ch == '&') { int semicolon = text.indexOf(';', start + 1); if (semicolon > 0) { String val = text.substring(start + 1, semicolon); if (val.startsWith("#")) { try { int value; if (val.length() > 2 && (val.charAt(1) == 'x' || val.charAt(1) == 'X')) { value = Integer.parseInt(val.substring(2), 16); } else { value = Integer.parseInt(val.substring(1), 10); } if (value >= 0 && value < 0xffff) { quotation = Character.toString((char) value); } } catch (NumberFormatException ex) { /* ignore, skip */ } } else { if (val.length() == 2) { if ("lt".equals(val)) { quotation = "<"; } else if ("gt".equals(val)) { quotation = ">"; } } else if ("amp".equals(val)) { quotation = "&"; } else if ("apos".equals(val)) { quotation = "'"; } else if ("quot".equals(val)) { quotation = "\""; } } if (quotation != null) { i = semicolon; } } } if (buffer == null) { if (quotation != null) { buffer = new StringBuilder(text.length()); // Copy previous skipped characters and fall through // to pickup current character buffer.append(text.substring(0, start)); buffer.append(quotation); } } else { if (quotation == null) { buffer.append(ch); } else { buffer.append(quotation); } } } // If there were any entities, return the escaped characters // that we put in the StringBuilder. Otherwise, just return // the unmodified input string. return buffer == null ? text : buffer.toString(); } }