/* * Copyright (C) 2010-2016 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.xfl; import com.jpexs.helpers.Helper; import java.util.HashMap; import java.util.Map; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.NamespaceContext; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; /** * * @author JPEXS */ public class XFLXmlWriter implements XMLStreamWriter { private static final Logger logger = Logger.getLogger(XFLXmlWriter.class.getName()); private final String newLineCharacters = "\n"; //Helper.newLine; private boolean newLine = true; private boolean newLineNeeded = false; private boolean startElementClosed = true; private final StringBuilder sb = new StringBuilder(); private final Map<String, String> namespaces = new HashMap<>(); private final Stack<String> tagsStack = new Stack<>(); @Override public String toString() { return sb.toString(); } private XFLXmlWriter append(char character) { sb.append(character); newLine = false; return this; } private XFLXmlWriter append(String text) { sb.append(text); newLine = false; return this; } private void makeNewLine() { if (!newLine) { sb.append(newLineCharacters); newLine = true; for (int i = 0; i < tagsStack.size(); i++) { sb.append(" "); } } } private void closeStartElement() { if (!startElementClosed) { append('>'); startElementClosed = true; } } private void ensureStartElementOpen() throws XMLStreamException { if (startElementClosed) { throw new XMLStreamException("Attempted to write attribute out of the start element"); } } private void closeStartElementNewLine() { closeStartElement(); makeNewLine(); } @Override public void writeStartElement(String localName) throws XMLStreamException { closeStartElementNewLine(); append('<').append(localName); tagsStack.add(localName); startElementClosed = false; newLineNeeded = false; } public void writeStartElement(String localName, String[] attributes) throws XMLStreamException { writeStartElement(localName); if (attributes.length % 2 != 0) { throw new XMLStreamException("Attribute count should be even"); } for (int i = 0; i < attributes.length / 2; i++) { writeAttribute(attributes[i * 2], attributes[i * 2 + 1]); } } @Override public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { writeStartElementInternal(getPrefix(namespaceURI), localName, namespaceURI); } @Override public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { setPrefix(prefix, namespaceURI); writeStartElementInternal(prefix, localName, namespaceURI); } private void writeStartElementInternal(String prefix, String localName, String namespaceURI) throws XMLStreamException { closeStartElementNewLine(); append('<').append(prefix).append(':').append(localName); writeNamespace(prefix, namespaceURI); tagsStack.add(localName); startElementClosed = false; newLineNeeded = false; } @Override public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { writeEmptyElementInternal(getPrefix(namespaceURI), localName, namespaceURI); } @Override public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { setPrefix(prefix, namespaceURI); writeEmptyElementInternal(prefix, localName, namespaceURI); } private void writeEmptyElementInternal(String prefix, String localName, String namespaceURI) throws XMLStreamException { writeStartElement(prefix, localName, namespaceURI); writeEndElement(); } @Override public void writeEmptyElement(String localName) throws XMLStreamException { writeStartElement(localName); writeEndElement(); } public void writeEmptyElement(String localName, String[] attributes) throws XMLStreamException { writeStartElement(localName, attributes); writeEndElement(); } @Override public void writeEndElement() throws XMLStreamException { String localName = tagsStack.pop(); if (startElementClosed) { if (newLineNeeded) { makeNewLine(); } append("</").append(localName).append('>'); } else { append(" />"); startElementClosed = true; } newLineNeeded = true; } public void writeElementValue(String localName, String value) throws XMLStreamException { writeStartElement(localName); writeCharacters(value); writeEndElement(); } public void writeElementValue(String localName, float value) throws XMLStreamException { writeElementValue(localName, Float.toString(value)); } public void writeElementValue(String localName, double value) throws XMLStreamException { writeElementValue(localName, Double.toString(value)); } public void writeElementValue(String localName, int value) throws XMLStreamException { writeElementValue(localName, Integer.toString(value)); } public void writeElementValue(String localName, long value) throws XMLStreamException { writeElementValue(localName, Long.toString(value)); } public void writeElementValueRaw(String localName, String value) throws XMLStreamException { writeStartElement(localName); writeCharactersRaw(value); writeEndElement(); } public void writeElementValue(String localName, String value, String[] attributes) throws XMLStreamException { writeStartElement(localName, attributes); writeCharacters(value); writeEndElement(); } @Override public void writeEndDocument() throws XMLStreamException { } @Override public void close() throws XMLStreamException { } @Override public void flush() throws XMLStreamException { } @Override public void writeAttribute(String localName, String value) throws XMLStreamException { ensureStartElementOpen(); append(' ').append(localName).append("=\"").append(escapeAttribute(value)).append('"'); } public void writeAttribute(String localName, float value) throws XMLStreamException { writeAttribute(localName, Float.toString(value)); } public void writeAttribute(String localName, double value) throws XMLStreamException { writeAttribute(localName, Double.toString(value)); } public void writeAttribute(String localName, int value) throws XMLStreamException { writeAttribute(localName, Integer.toString(value)); } public void writeAttribute(String localName, long value) throws XMLStreamException { writeAttribute(localName, Long.toString(value)); } public void writeAttribute(String localName, boolean value) throws XMLStreamException { writeAttribute(localName, value ? "true" : "false"); } @Override public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException { setPrefix(prefix, namespaceURI); writeAttributeInternal(prefix, localName, value); } @Override public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { writeAttributeInternal(getPrefix(namespaceURI), localName, value); } private void writeAttributeInternal(String prefix, String localName, String value) throws XMLStreamException { ensureStartElementOpen(); append(' ').append(prefix).append(':').append(localName).append("=\"").append(escapeAttribute(value)).append('"'); } @Override public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { writeAttribute("xmlns", null, prefix, namespaceURI); } @Override public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { } @Override public void writeComment(String data) throws XMLStreamException { closeStartElement(); append("<!--").append(data).append("-->"); } @Override public void writeProcessingInstruction(String target) throws XMLStreamException { } @Override public void writeProcessingInstruction(String target, String data) throws XMLStreamException { } @Override public void writeCData(String data) throws XMLStreamException { closeStartElement(); // todo: split when data cintains "]]>" append("<![CDATA[").append(data).append("]]>"); } @Override public void writeDTD(String dtd) throws XMLStreamException { } @Override public void writeEntityRef(String name) throws XMLStreamException { } @Override public void writeStartDocument() throws XMLStreamException { } @Override public void writeStartDocument(String version) throws XMLStreamException { } @Override public void writeStartDocument(String encoding, String version) throws XMLStreamException { } @Override public void writeCharacters(String text) throws XMLStreamException { closeStartElement(); append(escapeText(text)); } public void writeCharactersRaw(String text) throws XMLStreamException { closeStartElement(); append(text); } @Override public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { writeCharacters(new String(text, start, len)); } @Override public String getPrefix(String uri) throws XMLStreamException { String prefix = namespaces.get(uri); return prefix; } @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { namespaces.put(prefix, prefix); } @Override public void setDefaultNamespace(String uri) throws XMLStreamException { } @Override public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { } @Override public NamespaceContext getNamespaceContext() { return null; } @Override public Object getProperty(String name) throws IllegalArgumentException { return null; } public static String escapeText(String text) { //String[] from = new String[]{"&", "<"}; //String[] to = new String[]{"&", "<"}; // temporary escape everything to make easier to compare the export with the old export String[] from = new String[]{"&", "<", "\"", "'", "\r", "\n"}; String[] to = new String[]{"&", "<", """, "'", " ", " "}; for (int i = 0; i < from.length; i++) { text = text.replace(from[i], to[i]); } if (Helper.containsInvalidXMLCharacter(text)) { logger.log(Level.WARNING, "The following text contains a character which is invalid in XML: {0}", text); return Helper.removeInvalidXMLCharacters(text); } return text; } public static String escapeAttribute(String text) { String[] from = new String[]{"&", "<", "\"", "'", "\r", "\n"}; String[] to = new String[]{"&", "<", """, "'", " ", " "}; for (int i = 0; i < from.length; i++) { text = text.replace(from[i], to[i]); } if (Helper.containsInvalidXMLCharacter(text)) { logger.log(Level.WARNING, "The following text contains a character which is invalid in XML: {0}", text); return Helper.removeInvalidXMLCharacters(text); } return text; } public int length() { return sb.length(); } // todo: remove public void setLength(int newLength) { sb.setLength(newLength); } public boolean isEmpty() { return sb.length() == 0; } }