XMLElement.java
1/*******************************************************************************
2 * Copyright (c) 2009 Mountainminds GmbH & Co. KG and others
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 * $Id: $
12 *******************************************************************************/
13package org.jacoco.report.xml;
14
15import static java.lang.String.format;
16
17import java.io.IOException;
18import java.io.Writer;
19
20/**
21 * Simple API to create well formed XML streams. A {@link XMLElement} instance
22 * represents a single element in a XML document.
23 *
24 * @see XMLDocument
25 * @author Marc R. Hoffmann
26 * @version $Revision: $
27 */
28public class XMLElement {
29
30 private static final char SPACE = ' ';
31
32 private static final char EQ = '=';
33
34 private static final char LT = '<';
35
36 private static final char GT = '>';
37
38 private static final char QUOT = '"';
39
40 private static final char AMP = '&';
41
42 private static final char SLASH = '/';
43
44 /** Writer for content output */
45 protected final Writer writer;
46
47 private final String name;
48
49 private boolean openTagDone;
50
51 private boolean closed;
52
53 private XMLElement lastchild;
54
55 /**
56 * Creates a new element for a XML document.
57 *
58 * @param writer
59 * all output will be written directly to this
60 * @param name
61 * element name
62 */
63 protected XMLElement(final Writer writer, final String name) {
64 this.writer = writer;
65 this.name = name;
66 this.openTagDone = false;
67 this.closed = false;
68 this.lastchild = null;
69 }
70
71 /**
72 * Emits the beginning of the open tag. This method has to be called before
73 * other other methods are called on this element.
74 *
75 * @throws IOException
76 * in case of problems with the writer
77 */
78 protected void beginOpenTag() throws IOException {
79 writer.write(LT);
80 writer.write(name);
81 }
82
83 private void finishOpenTag() throws IOException {
84 if (!openTagDone) {
85 writer.append(GT);
86 openTagDone = true;
87 }
88 }
89
90 /**
91 * Adds the given child to this element. This will close all previous child
92 * elements.
93 *
94 * @param child
95 * child element to add
96 * @throws IOException
97 * in case of invalid nesting or problems with the writer
98 */
99 protected void addChildElement(final XMLElement child) throws IOException {
100 if (closed) {
101 throw new IOException(format("Element %s already closed.", name));
102 }
103 finishOpenTag();
104 if (lastchild != null) {
105 lastchild.close();
106 }
107 child.beginOpenTag();
108 lastchild = child;
109 }
110
111 private void quote(final String text) throws IOException {
112 final int len = text.length();
113 for (int i = 0; i < len; i++) {
114 final char c = text.charAt(i);
115 switch (c) {
116 case LT:
117 writer.write("<");
118 break;
119 case GT:
120 writer.write(">");
121 break;
122 case QUOT:
123 writer.write(""");
124 break;
125 case AMP:
126 writer.write("&");
127 break;
128 default:
129 writer.write(c);
130 }
131 }
132 }
133
134 /**
135 * Adds an attribute to this element. May only be called before an child
136 * element is added or this element has been closed. The attribute value
137 * will be quoted.
138 *
139 * @param name
140 * attribute name
141 * @param value
142 * attribute value
143 *
144 * @return this element
145 * @throws IOException
146 * in case of problems with the writer
147 */
148 public XMLElement attr(final String name, final String value)
149 throws IOException {
150 if (closed || openTagDone) {
151 throw new IOException(format("Element %s already closed.",
152 this.name));
153 }
154 writer.write(SPACE);
155 writer.write(name);
156 writer.write(EQ);
157 writer.write(QUOT);
158 quote(value);
159 writer.write(QUOT);
160 return this;
161 }
162
163 /**
164 * Adds the given text as a child to this node. The text will be quoted.
165 *
166 * @param text
167 * text to add
168 * @return this element
169 * @throws IOException
170 * in case of problems with the writer
171 */
172 public XMLElement text(final String text) throws IOException {
173 if (closed) {
174 throw new IOException(format("Element %s already closed.", name));
175 }
176 finishOpenTag();
177 if (lastchild != null) {
178 lastchild.close();
179 }
180 quote(text);
181 return this;
182 }
183
184 /**
185 * Creates a new child element for this element,
186 *
187 * @param name
188 * name of the child element
189 * @return child element instance
190 * @throws IOException
191 * in case of problems with the writer
192 */
193 public XMLElement element(final String name) throws IOException {
194 final XMLElement element = new XMLElement(writer, name);
195 addChildElement(element);
196 return element;
197 }
198
199 /**
200 * Closes this element if it has not been closed before.
201 *
202 * @throws IOException
203 * in case of problems with the writer
204 */
205 public void close() throws IOException {
206 if (!closed) {
207 if (lastchild != null) {
208 lastchild.close();
209 }
210 if (openTagDone) {
211 writer.write(LT);
212 writer.write(SLASH);
213 writer.write(name);
214 } else {
215 writer.write(SLASH);
216 }
217 writer.write(GT);
218 closed = true;
219 openTagDone = true;
220 }
221 }
222
223}