XMLElement.java revision 398ee59bebad6835dab57b60157eff16d511709e
1/******************************************************************************* 2 * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 *******************************************************************************/ 12package org.jacoco.report.internal.xml; 13 14import static java.lang.String.format; 15 16import java.io.IOException; 17import java.io.Writer; 18 19/** 20 * Simple API to create well formed XML streams. A {@link XMLElement} instance 21 * represents a single element in a XML document. 22 * 23 * @see XMLDocument 24 */ 25public class XMLElement { 26 27 private static final char SPACE = ' '; 28 29 private static final char EQ = '='; 30 31 private static final char LT = '<'; 32 33 private static final char GT = '>'; 34 35 private static final char QUOT = '"'; 36 37 private static final char AMP = '&'; 38 39 private static final char SLASH = '/'; 40 41 /** Writer for content output */ 42 protected final Writer writer; 43 44 private final String name; 45 46 private boolean openTagDone; 47 48 private boolean closed; 49 50 private XMLElement lastchild; 51 52 /** 53 * Creates a new element for a XML document. 54 * 55 * @param writer 56 * all output will be written directly to this 57 * @param name 58 * element name 59 */ 60 protected XMLElement(final Writer writer, final String name) { 61 this.writer = writer; 62 this.name = name; 63 this.openTagDone = false; 64 this.closed = false; 65 this.lastchild = null; 66 } 67 68 /** 69 * Emits the beginning of the open tag. This method has to be called before 70 * other other methods are called on this element. 71 * 72 * @throws IOException 73 * in case of problems with the writer 74 */ 75 protected void beginOpenTag() throws IOException { 76 writer.write(LT); 77 writer.write(name); 78 } 79 80 private void finishOpenTag() throws IOException { 81 if (!openTagDone) { 82 writer.append(GT); 83 openTagDone = true; 84 } 85 } 86 87 /** 88 * Adds the given child to this element. This will close all previous child 89 * elements. 90 * 91 * @param child 92 * child element to add 93 * @throws IOException 94 * in case of invalid nesting or problems with the writer 95 */ 96 protected void addChildElement(final XMLElement child) throws IOException { 97 if (closed) { 98 throw new IOException(format("Element %s already closed.", name)); 99 } 100 finishOpenTag(); 101 if (lastchild != null) { 102 lastchild.close(); 103 } 104 child.beginOpenTag(); 105 lastchild = child; 106 } 107 108 private void quote(final String text) throws IOException { 109 final int len = text.length(); 110 for (int i = 0; i < len; i++) { 111 final char c = text.charAt(i); 112 switch (c) { 113 case LT: 114 writer.write("<"); 115 break; 116 case GT: 117 writer.write(">"); 118 break; 119 case QUOT: 120 writer.write("""); 121 break; 122 case AMP: 123 writer.write("&"); 124 break; 125 default: 126 writer.write(c); 127 break; 128 } 129 } 130 } 131 132 /** 133 * Adds an attribute to this element. May only be called before an child 134 * element is added or this element has been closed. The attribute value 135 * will be quoted. If the value is <code>null</code> the attribute will not 136 * be added. 137 * 138 * @param name 139 * attribute name 140 * @param value 141 * attribute value or <code>null</code> 142 * 143 * @return this element 144 * @throws IOException 145 * in case of problems with the writer 146 */ 147 public XMLElement attr(final String name, final String value) 148 throws IOException { 149 if (value == null) { 150 return this; 151 } 152 if (closed || openTagDone) { 153 throw new IOException(format("Element %s already closed.", 154 this.name)); 155 } 156 writer.write(SPACE); 157 writer.write(name); 158 writer.write(EQ); 159 writer.write(QUOT); 160 quote(value); 161 writer.write(QUOT); 162 return this; 163 } 164 165 /** 166 * Adds an attribute to this element. May only be called before an child 167 * element is added or this element has been closed. The attribute value is 168 * the decimal representation of the given int value. 169 * 170 * @param name 171 * attribute name 172 * @param value 173 * attribute value 174 * 175 * @return this element 176 * @throws IOException 177 * in case of problems with the writer 178 */ 179 public XMLElement attr(final String name, final int value) 180 throws IOException { 181 return attr(name, String.valueOf(value)); 182 } 183 184 /** 185 * Adds an attribute to this element. May only be called before an child 186 * element is added or this element has been closed. The attribute value is 187 * the decimal representation of the given long value. 188 * 189 * @param name 190 * attribute name 191 * @param value 192 * attribute value 193 * 194 * @return this element 195 * @throws IOException 196 * in case of problems with the writer 197 */ 198 public XMLElement attr(final String name, final long value) 199 throws IOException { 200 return attr(name, String.valueOf(value)); 201 } 202 203 /** 204 * Adds the given text as a child to this node. The text will be quoted. 205 * 206 * @param text 207 * text to add 208 * @return this element 209 * @throws IOException 210 * in case of problems with the writer 211 */ 212 public XMLElement text(final String text) throws IOException { 213 if (closed) { 214 throw new IOException(format("Element %s already closed.", name)); 215 } 216 finishOpenTag(); 217 if (lastchild != null) { 218 lastchild.close(); 219 } 220 quote(text); 221 return this; 222 } 223 224 /** 225 * Creates a new child element for this element, 226 * 227 * @param name 228 * name of the child element 229 * @return child element instance 230 * @throws IOException 231 * in case of problems with the writer 232 */ 233 public XMLElement element(final String name) throws IOException { 234 final XMLElement element = new XMLElement(writer, name); 235 addChildElement(element); 236 return element; 237 } 238 239 /** 240 * Closes this element if it has not been closed before. 241 * 242 * @throws IOException 243 * in case of problems with the writer 244 */ 245 public void close() throws IOException { 246 if (!closed) { 247 if (lastchild != null) { 248 lastchild.close(); 249 } 250 if (openTagDone) { 251 writer.write(LT); 252 writer.write(SLASH); 253 writer.write(name); 254 } else { 255 writer.write(SLASH); 256 } 257 writer.write(GT); 258 closed = true; 259 openTagDone = true; 260 } 261 } 262 263} 264