| /******************************************************************************* |
| * Copyright (c) 2007 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.persistence; |
| |
| import java.io.*; |
| import java.util.*; |
| import org.osgi.framework.Version; |
| |
| public class XMLWriter implements XMLConstants { |
| |
| public static class ProcessingInstruction { |
| |
| private String target; |
| private String[] data; |
| |
| // The standard UTF-8 processing instruction |
| public static final String XML_UTF8 = "<?xml version='1.0' encoding='UTF-8'?>"; //$NON-NLS-1$ |
| |
| public ProcessingInstruction(String target, String[] attrs, String[] values) { |
| // Lengths of attributes and values must be the same |
| this.target = target; |
| this.data = new String[attrs.length]; |
| for (int i = 0; i < attrs.length; i++) { |
| data[i] = attributeImage(attrs[i], values[i]); |
| } |
| } |
| |
| public static ProcessingInstruction makeClassVersionInstruction(String target, Class clazz, Version version) { |
| return new ProcessingInstruction(target, new String[] {PI_CLASS_ATTRIBUTE, PI_VERSION_ATTRIBUTE}, new String[] {clazz.getName(), version.toString()}); |
| } |
| |
| public String toString() { |
| StringBuffer sb = new StringBuffer("<?"); //$NON-NLS-1$ |
| sb.append(this.target).append(' '); |
| for (int i = 0; i < data.length; i++) { |
| sb.append(this.data[i]); |
| if (i < data.length - 1) { |
| sb.append(' '); |
| } |
| } |
| sb.append("?>"); //$NON-NLS-1$ |
| return sb.toString(); |
| } |
| } |
| |
| private Stack elements; // XML elements that have not yet been closed |
| private boolean open; // Can attributes be added to the current element? |
| private String indent; // used for each level of indentation |
| |
| private PrintWriter pw; |
| |
| public XMLWriter(OutputStream output, ProcessingInstruction[] piElements) throws UnsupportedEncodingException { |
| this.pw = new PrintWriter(new OutputStreamWriter(output, "UTF8"), false); //$NON-NLS-1$ |
| println(ProcessingInstruction.XML_UTF8); |
| this.elements = new Stack(); |
| this.open = false; |
| this.indent = " "; //$NON-NLS-1$ |
| if (piElements != null) { |
| for (int i = 0; i < piElements.length; i++) { |
| println(piElements[i].toString()); |
| } |
| } |
| } |
| |
| // start a new element |
| public void start(String name) { |
| if (this.open) { |
| println('>'); |
| } |
| indent(); |
| print('<'); |
| print(name); |
| this.elements.push(name); |
| this.open = true; |
| } |
| |
| // end the most recent element with this name |
| public void end(String name) { |
| if (this.elements.empty()) { |
| throw new EndWithoutStartError(); |
| } |
| int index = this.elements.search(name); |
| if (index == -1) { |
| throw new EndWithoutStartError(name); |
| } |
| for (int i = 0; i < index; i += 1) { |
| end(); |
| } |
| } |
| |
| // end the current element |
| public void end() { |
| if (this.elements.empty()) { |
| throw new EndWithoutStartError(); |
| } |
| String name = (String) this.elements.pop(); |
| if (this.open) { |
| println("/>"); //$NON-NLS-1$ |
| } else { |
| printlnIndented("</" + name + '>', false); //$NON-NLS-1$ |
| } |
| this.open = false; |
| } |
| |
| public static String escape(String txt) { |
| StringBuffer buffer = null; |
| for (int i = 0; i < txt.length(); ++i) { |
| String replace; |
| char c = txt.charAt(i); |
| switch (c) { |
| case '<' : |
| replace = "<"; //$NON-NLS-1$ |
| break; |
| case '>' : |
| replace = ">"; //$NON-NLS-1$ |
| break; |
| case '"' : |
| replace = """; //$NON-NLS-1$ |
| break; |
| case '\'' : |
| replace = "'"; //$NON-NLS-1$ |
| break; |
| case '&' : |
| replace = "&"; //$NON-NLS-1$ |
| break; |
| case '\t' : |
| replace = "	"; //$NON-NLS-1$ |
| break; |
| case '\n' : |
| replace = "
"; //$NON-NLS-1$ |
| break; |
| case '\r' : |
| replace = "
"; //$NON-NLS-1$ |
| break; |
| default : |
| // this is the set of legal xml scharacters in unicode excluding high surrogates since they cannot be represented with a char |
| // see http://www.w3.org/TR/REC-xml/#charsets |
| if ((c >= '\u0020' && c <= '\uD7FF') || (c >= '\uE000' && c <= '\uFFFD')) { |
| if (buffer != null) |
| buffer.append(c); |
| continue; |
| } |
| replace = Character.isWhitespace(c) ? " " : null; //$NON-NLS-1$ |
| } |
| if (buffer == null) { |
| buffer = new StringBuffer(txt.length() + 16); |
| buffer.append(txt.substring(0, i)); |
| } |
| if (replace != null) |
| buffer.append(replace); |
| } |
| |
| if (buffer == null) |
| return txt; |
| |
| return buffer.toString(); |
| } |
| |
| // write a boolean attribute if it doesn't have the default value |
| public void attribute(String name, boolean value, boolean defaultValue) { |
| if (value != defaultValue) { |
| attribute(name, value); |
| } |
| } |
| |
| public void attribute(String name, boolean value) { |
| attribute(name, Boolean.toString(value)); |
| } |
| |
| public void attribute(String name, int value) { |
| attribute(name, Integer.toString(value)); |
| } |
| |
| public void attributeOptional(String name, String value) { |
| if (value != null && value.length() > 0) { |
| attribute(name, value); |
| } |
| } |
| |
| public void attribute(String name, Object value) { |
| if (!this.open) { |
| throw new AttributeAfterNestedContentError(); |
| } |
| if (value == null) { |
| return; // optional attribute with no value |
| } |
| print(' '); |
| print(name); |
| print("='"); //$NON-NLS-1$ |
| print(escape(value.toString())); |
| print('\''); |
| } |
| |
| public void cdata(String data) { |
| cdata(data, true); |
| } |
| |
| public void cdata(String data, boolean escape) { |
| if (this.open) { |
| println('>'); |
| this.open = false; |
| } |
| if (data != null) { |
| printlnIndented(data, escape); |
| } |
| } |
| |
| public void flush() { |
| this.pw.flush(); |
| } |
| |
| public void writeProperties(Map properties) { |
| writeProperties(PROPERTIES_ELEMENT, properties); |
| } |
| |
| public void writeProperties(String propertiesElement, Map properties) { |
| if (properties != null && properties.size() > 0) { |
| start(propertiesElement); |
| attribute(COLLECTION_SIZE_ATTRIBUTE, properties.size()); |
| for (Iterator iter = properties.keySet().iterator(); iter.hasNext();) { |
| String name = (String) iter.next(); |
| writeProperty(name, (String) properties.get(name)); |
| } |
| end(propertiesElement); |
| } |
| } |
| |
| public void writeProperty(String name, String value) { |
| start(PROPERTY_ELEMENT); |
| attribute(PROPERTY_NAME_ATTRIBUTE, name); |
| attribute(PROPERTY_VALUE_ATTRIBUTE, value); |
| end(); |
| } |
| |
| protected static String attributeImage(String name, String value) { |
| if (value == null) { |
| return ""; // optional attribute with no value |
| } |
| return name + "='" + escape(value) + '\''; //$NON-NLS-1$ |
| } |
| |
| private void println(char c) { |
| this.pw.println(c); |
| } |
| |
| private void println(String s) { |
| this.pw.println(s); |
| } |
| |
| private void println() { |
| this.pw.println(); |
| } |
| |
| private void print(char c) { |
| this.pw.print(c); |
| } |
| |
| private void print(String s) { |
| this.pw.print(s); |
| } |
| |
| private void printlnIndented(String s, boolean escape) { |
| if (s.length() == 0) { |
| println(); |
| } else { |
| indent(); |
| println(escape ? escape(s) : s); |
| } |
| } |
| |
| private void indent() { |
| for (int i = this.elements.size(); i > 0; i -= 1) { |
| print(this.indent); |
| } |
| } |
| |
| public static class AttributeAfterNestedContentError extends Error { |
| private static final long serialVersionUID = 1L; // not serialized |
| } |
| |
| public static class EndWithoutStartError extends Error { |
| private static final long serialVersionUID = 1L; // not serialized |
| private String name; |
| |
| public EndWithoutStartError() { |
| super(); |
| } |
| |
| public EndWithoutStartError(String name) { |
| super(); |
| this.name = name; |
| } |
| |
| public String getName() { |
| return this.name; |
| } |
| } |
| |
| } |