Home | History | Annotate | Download | only in reporters
      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 (at) 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 
     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    */
     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    */
     51   public XMLStringBuffer(IBuffer buffer, String start) {
     52     init(buffer, start);
     53   }
     54 
     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   */
     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    */
     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    */
     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    */
    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    */
    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    */
    126   public void push(String tagName, @Nullable Properties attributes) {
    127     push(tagName, "", attributes);
    128   }
    129 
    130   public void push(String tagName, String... attributes) {
    131     push(tagName, createProperties(attributes));
    132   }
    133 
    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    */
    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    */
    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    */
    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    */
    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    */
    203   public void addRequired(String tagName, @Nullable String value, @Nullable Properties attributes) {
    204     XMLUtils.xmlRequired(m_buffer, m_currentIndent, tagName, value, attributes);
    205   }
    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    */
    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 
    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    */
    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    */
    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    */
    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    */
    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    */
    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 
    283   public void addEmptyElement(String tagName, String... attributes) {
    284     addEmptyElement(tagName, createProperties(attributes));
    285   }
    286 
    287   public void addComment(String comment) {
    288     m_buffer.append(m_currentIndent).append("<!-- " + comment.replaceAll("[-]{2,}", "-") + " -->\n");
    289   }
    290 
    291   public void addString(String s) {
    292     m_buffer.append(s);
    293   }
    294 
    295   private static void ppp(String s) {
    296     System.out.println("[XMLStringBuffer] " + s);
    297   }
    298 
    299   /**
    300    * Add a CDATA tag.
    301    */
    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    */
    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    */
    336   public String toXML() {
    337     return INVALID_XML_CHARS.matcher(m_buffer.toString()).replaceAll("");
    338   }
    339 
    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 
    359   public String getCurrentIndent() {
    360     return m_currentIndent;
    361   }
    362 
    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 
    376   public Tag(String ind, String n, Properties p) {
    377     tagName = n;
    378     indent = ind;
    379     properties = p;
    380   }
    381 
    382   @Override
    383   public String toString() {
    384     return tagName;
    385   }
    386 }
    387