• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.testng.reporters;
2 
3 import java.io.Writer;
4 import java.util.Properties;
5 import java.util.Stack;
6 import java.util.regex.Pattern;
7 
8 import org.testng.internal.Nullable;
9 
10 /**
11  * This class allows you to generate an XML text document by pushing
12  * and popping tags from a stack maintained internally.
13  *
14  * @author <a href="mailto:cedric@beust.com">Cedric Beust</a> Jul 21, 2003
15  */
16 public class XMLStringBuffer {
17   /** End of line, value of 'line.separator' system property or '\n' */
18   private static final String EOL = System.getProperty("line.separator", "\n");
19 
20   /** Tab space indent for XML document */
21   private static final String DEFAULT_INDENT_INCREMENT = "  ";
22 
23   /** The buffer to hold the xml document */
24   private IBuffer m_buffer;
25 
26   /** The stack of tags to make sure XML document is well formed. */
27   private final Stack<Tag> m_tagStack = new Stack<>();
28 
29   /** A string of space character representing the current indentation. */
30   private String m_currentIndent = "";
31 
XMLStringBuffer()32   public XMLStringBuffer() {
33     init(Buffer.create(), "", "1.0", "UTF-8");
34   }
35 
36   /**
37    * @param start A string of spaces indicating the indentation at which
38    * to start the generation. This constructor will not insert an <?xml
39    * prologue.
40    */
XMLStringBuffer(String start)41   public XMLStringBuffer(String start) {
42     init(Buffer.create(), start);
43   }
44 
45   /**
46    * @param buffer The StringBuffer to use internally to represent the
47    * document.
48    * @param start A string of spaces indicating the indentation at which
49    * to start the generation.
50    */
XMLStringBuffer(IBuffer buffer, String start)51   public XMLStringBuffer(IBuffer buffer, String start) {
52     init(buffer, start);
53   }
54 
init(IBuffer buffer, String start)55   private void init(IBuffer buffer, String start) {
56     init(buffer, start, null, null);
57   }
58 
59   /**
60   *
61   * @param start A string of spaces indicating the indentation at which
62   * to start the generation.
63   */
init(IBuffer buffer, String start, @Nullable String version, @Nullable String encoding)64   private void init(IBuffer buffer, String start, @Nullable String version, @Nullable String encoding) {
65     m_buffer = buffer;
66     m_currentIndent = start;
67     if (version != null) {
68       setXmlDetails(version, encoding);
69     }
70   }
71 
72  /**
73    * Set the xml version and encoding for this document.
74    *
75    * @param v the XML version
76    * @param enc the XML encoding
77    */
setXmlDetails(String v, String enc)78   public void setXmlDetails(String v, String enc) {
79     if (m_buffer.toString().length() != 0) {
80       throw new IllegalStateException("Buffer should be empty: '" + m_buffer.toString() + "'");
81     }
82     m_buffer.append("<?xml version=\"" + v + "\" encoding=\"" + enc + "\"?>").append(EOL);
83   }
84 
85   /**
86    * Set the doctype for this document.
87    *
88    * @param docType The DOCTYPE string, without the "&lt;!DOCTYPE " "&gt;"
89    */
setDocType(String docType)90   public void setDocType(String docType) {
91     m_buffer.append("<!DOCTYPE " + docType + ">" + EOL);
92   }
93 
94   /**
95    * Push a new tag.  Its value is stored and will be compared against the parameter
96    * passed to pop().
97    *
98    * @param tagName The name of the tag.
99    * @param schema The schema to use (can be null or an empty string).
100    * @param attributes A Properties file representing the attributes (or null)
101    */
push(String tagName, @Nullable String schema, @Nullable Properties attributes)102   public void push(String tagName, @Nullable String schema, @Nullable Properties attributes) {
103     XMLUtils.xmlOpen(m_buffer, m_currentIndent, tagName + schema, attributes);
104     m_tagStack.push(new Tag(m_currentIndent, tagName, attributes));
105     m_currentIndent += DEFAULT_INDENT_INCREMENT;
106   }
107 
108   /**
109    * Push a new tag.  Its value is stored and will be compared against the parameter
110    * passed to pop().
111    *
112    * @param tagName The name of the tag.
113    * @param schema The schema to use (can be null or an empty string).
114    */
push(String tagName, @Nullable String schema)115   public void push(String tagName, @Nullable String schema) {
116     push(tagName, schema, null);
117   }
118 
119   /**
120    * Push a new tag.  Its value is stored and will be compared against the parameter
121    * passed to pop().
122    *
123    * @param tagName The name of the tag.
124    * @param attributes A Properties file representing the attributes (or null)
125    */
push(String tagName, @Nullable Properties attributes)126   public void push(String tagName, @Nullable Properties attributes) {
127     push(tagName, "", attributes);
128   }
129 
push(String tagName, String... attributes)130   public void push(String tagName, String... attributes) {
131     push(tagName, createProperties(attributes));
132   }
133 
createProperties(String[] attributes)134   private Properties createProperties(String[] attributes) {
135     Properties result = new Properties();
136     if (attributes == null) {
137       return result;
138     }
139     if (attributes.length % 2 != 0) {
140       throw new IllegalArgumentException("Arguments 'attributes' length must be even. Actual: " + attributes.length);
141     }
142     for (int i = 0; i < attributes.length; i += 2) {
143       result.put(attributes[i], attributes[i + 1]);
144     }
145     return result;
146   }
147 
148   /**
149    * Push a new tag.  Its value is stored and will be compared against the parameter
150    * passed to pop().
151    *
152    * @param tagName The name of the tag.
153    */
push(String tagName)154   public void push(String tagName) {
155     push(tagName, "");
156   }
157 
158   /**
159    * Pop the last pushed element without verifying it if matches the previously
160    * pushed tag.
161    */
pop()162   public void pop() {
163     pop(null);
164   }
165 
166   /**
167    * Pop the last pushed element and throws an AssertionError if it doesn't
168    * match the corresponding tag that was pushed earlier.
169    *
170    * @param tagName The name of the tag this pop() is supposed to match.
171    */
pop(String tagName)172   public void pop(String tagName) {
173     m_currentIndent = m_currentIndent.substring(DEFAULT_INDENT_INCREMENT.length());
174     Tag t = m_tagStack.pop();
175     if (null != tagName) {
176       if (!tagName.equals(t.tagName)) {
177         // TODO Is it normal to throw an Error here?
178         throw new AssertionError(
179             "Popping the wrong tag: " + t.tagName + " but expected " + tagName);
180       }
181     }
182     XMLUtils.xmlClose(m_buffer, m_currentIndent, t.tagName,
183         XMLUtils.extractComment(tagName, t.properties));
184   }
185 
186   /**
187    * Add a required element to the current tag.  An opening and closing tag
188    * will be generated even if value is null.
189    * @param tagName The name of the tag
190    * @param value The value for this tag
191    */
addRequired(String tagName, @Nullable String value)192   public void addRequired(String tagName, @Nullable String value) {
193     addRequired(tagName, value, (Properties) null);
194   }
195 
196   /**
197    * Add a required element to the current tag.  An opening and closing tag
198    * will be generated even if value is null.
199    * @param tagName The name of the tag
200    * @param value The value for this tag
201    * @param attributes A Properties file containing the attributes (or null)
202    */
addRequired(String tagName, @Nullable String value, @Nullable Properties attributes)203   public void addRequired(String tagName, @Nullable String value, @Nullable Properties attributes) {
204     XMLUtils.xmlRequired(m_buffer, m_currentIndent, tagName, value, attributes);
205   }
addRequired(String tagName, @Nullable String value, String... attributes)206   public void addRequired(String tagName, @Nullable String value, String... attributes) {
207     addRequired(tagName, value, createProperties(attributes));
208   }
209 
210   /**
211    * Add an optional String element to the current tag.  If value is null, nothing is
212    * added.
213    * @param tagName The name of the tag
214    * @param value The value for this tag
215    * @param attributes A Properties file containing the attributes (or null)
216    */
addOptional(String tagName, @Nullable String value, @Nullable Properties attributes)217   public void addOptional(String tagName, @Nullable String value, @Nullable Properties attributes) {
218     if (value != null) {
219       XMLUtils.xmlOptional(m_buffer, m_currentIndent, tagName, value, attributes);
220     }
221   }
222 
addOptional(String tagName, @Nullable String value, String... attributes)223   public void addOptional(String tagName, @Nullable String value, String... attributes) {
224     if (value != null) {
225       XMLUtils.xmlOptional(m_buffer, m_currentIndent, tagName, value, createProperties(attributes));
226     }
227   }
228 
229   /**
230    * Add an optional String element to the current tag.  If value is null, nothing is
231    * added.
232    * @param tagName The name of the tag
233    * @param value The value for this tag
234    */
addOptional(String tagName, @Nullable String value)235   public void addOptional(String tagName, @Nullable String value) {
236     addOptional(tagName, value, (Properties) null);
237   }
238 
239   /**
240    * Add an optional Boolean element to the current tag.  If value is null, nothing is
241    * added.
242    * @param tagName The name of the tag
243    * @param value The value for this tag
244    * @param attributes A Properties file containing the attributes (or null)
245    */
addOptional(String tagName, @Nullable Boolean value, @Nullable Properties attributes)246   public void addOptional(String tagName, @Nullable Boolean value, @Nullable Properties attributes) {
247     if (null != value) {
248       XMLUtils.xmlOptional(m_buffer, m_currentIndent, tagName, value.toString(), attributes);
249     }
250   }
251 
252   /**
253    * Add an optional Boolean element to the current tag.  If value is null, nothing is
254    * added.
255    * @param tagName The name of the tag
256    * @param value The value for this tag
257    */
addOptional(String tagName, @Nullable Boolean value)258   public void addOptional(String tagName, @Nullable Boolean value) {
259     addOptional(tagName, value, null);
260   }
261 
262   /**
263    * Add an empty element tag (e.g. <foo/>)
264    *
265    * @param tagName The name of the tag
266    *
267    */
addEmptyElement(String tagName)268   public void addEmptyElement(String tagName) {
269     addEmptyElement(tagName, (Properties) null);
270   }
271 
272   /**
273    * Add an empty element tag (e.g. <foo/>)
274    * @param tagName The name of the tag
275    * @param attributes A Properties file containing the attributes (or null)
276    */
addEmptyElement(String tagName, @Nullable Properties attributes)277   public void addEmptyElement(String tagName, @Nullable Properties attributes) {
278     m_buffer.append(m_currentIndent).append("<").append(tagName);
279     XMLUtils.appendAttributes(m_buffer, attributes);
280     m_buffer.append("/>").append(EOL);
281   }
282 
addEmptyElement(String tagName, String... attributes)283   public void addEmptyElement(String tagName, String... attributes) {
284     addEmptyElement(tagName, createProperties(attributes));
285   }
286 
addComment(String comment)287   public void addComment(String comment) {
288     m_buffer.append(m_currentIndent).append("<!-- " + comment.replaceAll("[-]{2,}", "-") + " -->\n");
289   }
290 
addString(String s)291   public void addString(String s) {
292     m_buffer.append(s);
293   }
294 
ppp(String s)295   private static void ppp(String s) {
296     System.out.println("[XMLStringBuffer] " + s);
297   }
298 
299   /**
300    * Add a CDATA tag.
301    */
addCDATA(String content)302   public void addCDATA(String content) {
303     if (content == null) {
304       content = "null";
305     }
306     if (content.contains("]]>")) {
307       String[] subStrings = content.split("]]>");
308       m_buffer.append(m_currentIndent).append("<![CDATA[").append(subStrings[0]).append("]]]]>");
309       for (int i = 1; i < subStrings.length - 1; i++) {
310         m_buffer.append("<![CDATA[>").append(subStrings[i]).append("]]]]>");
311       }
312       m_buffer.append("<![CDATA[>").append(subStrings[subStrings.length - 1]).append("]]>");
313       if (content.endsWith("]]>")) {
314         m_buffer.append("<![CDATA[]]]]>").append("<![CDATA[>]]>");
315       }
316       m_buffer.append(EOL);
317     } else {
318       m_buffer.append(m_currentIndent).append("<![CDATA[").append(content).append("]]>" + EOL);
319     }
320   }
321 
322   /**
323    *
324    * @return The StringBuffer used to create the document.
325    */
getStringBuffer()326   public IBuffer getStringBuffer() {
327     return m_buffer;
328   }
329 
330   private static final Pattern INVALID_XML_CHARS =
331       Pattern.compile("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD\uD800\uDC00-\uDBFF\uDFFF]");
332 
333   /**
334    * @return The String representation of the XML for this XMLStringBuffer.
335    */
toXML()336   public String toXML() {
337     return INVALID_XML_CHARS.matcher(m_buffer.toString()).replaceAll("");
338   }
339 
main(String[] argv)340   public static void main(String[] argv) {
341     IBuffer result = Buffer.create();
342     XMLStringBuffer sb = new XMLStringBuffer(result, "");
343 
344     sb.push("family");
345     Properties p = new Properties();
346     p.setProperty("prop1", "value1");
347     p.setProperty("prop2", "value2");
348     sb.addRequired("cedric", "true", p);
349     sb.addRequired("alois", "true");
350     sb.addOptional("anne-marie", (String) null);
351     sb.pop();
352 
353     System.out.println(result.toString());
354 
355     assert ("<family>" + EOL + "<cedric>true</cedric>" + EOL + "<alois>true</alois>" + EOL + "</family>"  + EOL)
356       .equals(result.toString());
357   }
358 
getCurrentIndent()359   public String getCurrentIndent() {
360     return m_currentIndent;
361   }
362 
toWriter(Writer fw)363   public void toWriter(Writer fw) {
364     m_buffer.toWriter(fw);
365   }
366 }
367 
368 
369 ////////////////////////
370 
371 class Tag {
372   public final String tagName;
373   public final String indent;
374   public final Properties properties;
375 
Tag(String ind, String n, Properties p)376   public Tag(String ind, String n, Properties p) {
377     tagName = n;
378     indent = ind;
379     properties = p;
380   }
381 
382   @Override
toString()383   public String toString() {
384     return tagName;
385   }
386 }
387