Home | History | Annotate | Download | only in tagsoup
      1 // XMLWriter.java - serialize an XML document.
      2 // Written by David Megginson, david (at) megginson.com
      3 // and placed by him into the public domain.
      4 // Extensively modified by John Cowan for TagSoup.
      5 // TagSoup is licensed under the Apache License,
      6 // Version 2.0.  You may obtain a copy of this license at
      7 // http://www.apache.org/licenses/LICENSE-2.0 .  You may also have
      8 // additional legal rights not granted by this license.
      9 //
     10 // TagSoup is distributed in the hope that it will be useful, but
     11 // unless required by applicable law or agreed to in writing, TagSoup
     12 // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
     13 // OF ANY KIND, either express or implied; not even the implied warranty
     14 // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     15 
     16 package org.ccil.cowan.tagsoup;
     17 import org.xml.sax.Attributes;
     18 
     19 
     20 /**
     21  * Default implementation of the Attributes interface.
     22  *
     23  * <blockquote>
     24  * <em>This module, both source code and documentation, is in the
     25  * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
     26  * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
     27  * for further information.
     28  * </blockquote>
     29  *
     30  * <p>This class provides a default implementation of the SAX2
     31  * {@link org.xml.sax.Attributes Attributes} interface, with the
     32  * addition of manipulators so that the list can be modified or
     33  * reused.</p>
     34  *
     35  * <p>There are two typical uses of this class:</p>
     36  *
     37  * <ol>
     38  * <li>to take a persistent snapshot of an Attributes object
     39  *  in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or</li>
     40  * <li>to construct or modify an Attributes object in a SAX2 driver or filter.</li>
     41  * </ol>
     42  *
     43  * <p>This class replaces the now-deprecated SAX1 {@link
     44  * org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
     45  * class; in addition to supporting the updated Attributes
     46  * interface rather than the deprecated {@link org.xml.sax.AttributeList
     47  * AttributeList} interface, it also includes a much more efficient
     48  * implementation using a single array rather than a set of Vectors.</p>
     49  *
     50  * @since SAX 2.0
     51  * @author David Megginson
     52  * @version 2.0.1 (sax2r2)
     53  */
     54 public class AttributesImpl implements Attributes
     55 {
     56 
     57 
     58     ////////////////////////////////////////////////////////////////////
     60     // Constructors.
     61     ////////////////////////////////////////////////////////////////////
     62 
     63 
     64     /**
     65      * Construct a new, empty AttributesImpl object.
     66      */
     67     public AttributesImpl ()
     68     {
     69 	length = 0;
     70 	data = null;
     71     }
     72 
     73 
     74     /**
     75      * Copy an existing Attributes object.
     76      *
     77      * <p>This constructor is especially useful inside a
     78      * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
     79      *
     80      * @param atts The existing Attributes object.
     81      */
     82     public AttributesImpl (Attributes atts)
     83     {
     84 	setAttributes(atts);
     85     }
     86 
     87 
     88 
     89     ////////////////////////////////////////////////////////////////////
     91     // Implementation of org.xml.sax.Attributes.
     92     ////////////////////////////////////////////////////////////////////
     93 
     94 
     95     /**
     96      * Return the number of attributes in the list.
     97      *
     98      * @return The number of attributes in the list.
     99      * @see org.xml.sax.Attributes#getLength
    100      */
    101     public int getLength ()
    102     {
    103 	return length;
    104     }
    105 
    106 
    107     /**
    108      * Return an attribute's Namespace URI.
    109      *
    110      * @param index The attribute's index (zero-based).
    111      * @return The Namespace URI, the empty string if none is
    112      *         available, or null if the index is out of range.
    113      * @see org.xml.sax.Attributes#getURI
    114      */
    115     public String getURI (int index)
    116     {
    117 	if (index >= 0 && index < length) {
    118 	    return data[index*5];
    119 	} else {
    120 	    return null;
    121 	}
    122     }
    123 
    124 
    125     /**
    126      * Return an attribute's local name.
    127      *
    128      * @param index The attribute's index (zero-based).
    129      * @return The attribute's local name, the empty string if
    130      *         none is available, or null if the index if out of range.
    131      * @see org.xml.sax.Attributes#getLocalName
    132      */
    133     public String getLocalName (int index)
    134     {
    135 	if (index >= 0 && index < length) {
    136 	    return data[index*5+1];
    137 	} else {
    138 	    return null;
    139 	}
    140     }
    141 
    142 
    143     /**
    144      * Return an attribute's qualified (prefixed) name.
    145      *
    146      * @param index The attribute's index (zero-based).
    147      * @return The attribute's qualified name, the empty string if
    148      *         none is available, or null if the index is out of bounds.
    149      * @see org.xml.sax.Attributes#getQName
    150      */
    151     public String getQName (int index)
    152     {
    153 	if (index >= 0 && index < length) {
    154 	    return data[index*5+2];
    155 	} else {
    156 	    return null;
    157 	}
    158     }
    159 
    160 
    161     /**
    162      * Return an attribute's type by index.
    163      *
    164      * @param index The attribute's index (zero-based).
    165      * @return The attribute's type, "CDATA" if the type is unknown, or null
    166      *         if the index is out of bounds.
    167      * @see org.xml.sax.Attributes#getType(int)
    168      */
    169     public String getType (int index)
    170     {
    171 	if (index >= 0 && index < length) {
    172 	    return data[index*5+3];
    173 	} else {
    174 	    return null;
    175 	}
    176     }
    177 
    178 
    179     /**
    180      * Return an attribute's value by index.
    181      *
    182      * @param index The attribute's index (zero-based).
    183      * @return The attribute's value or null if the index is out of bounds.
    184      * @see org.xml.sax.Attributes#getValue(int)
    185      */
    186     public String getValue (int index)
    187     {
    188 	if (index >= 0 && index < length) {
    189 	    return data[index*5+4];
    190 	} else {
    191 	    return null;
    192 	}
    193     }
    194 
    195 
    196     /**
    197      * Look up an attribute's index by Namespace name.
    198      *
    199      * <p>In many cases, it will be more efficient to look up the name once and
    200      * use the index query methods rather than using the name query methods
    201      * repeatedly.</p>
    202      *
    203      * @param uri The attribute's Namespace URI, or the empty
    204      *        string if none is available.
    205      * @param localName The attribute's local name.
    206      * @return The attribute's index, or -1 if none matches.
    207      * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
    208      */
    209     public int getIndex (String uri, String localName)
    210     {
    211 	int max = length * 5;
    212 	for (int i = 0; i < max; i += 5) {
    213 	    if (data[i].equals(uri) && data[i+1].equals(localName)) {
    214 		return i / 5;
    215 	    }
    216 	}
    217 	return -1;
    218     }
    219 
    220 
    221     /**
    222      * Look up an attribute's index by qualified (prefixed) name.
    223      *
    224      * @param qName The qualified name.
    225      * @return The attribute's index, or -1 if none matches.
    226      * @see org.xml.sax.Attributes#getIndex(java.lang.String)
    227      */
    228     public int getIndex (String qName)
    229     {
    230 	int max = length * 5;
    231 	for (int i = 0; i < max; i += 5) {
    232 	    if (data[i+2].equals(qName)) {
    233 		return i / 5;
    234 	    }
    235 	}
    236 	return -1;
    237     }
    238 
    239 
    240     /**
    241      * Look up an attribute's type by Namespace-qualified name.
    242      *
    243      * @param uri The Namespace URI, or the empty string for a name
    244      *        with no explicit Namespace URI.
    245      * @param localName The local name.
    246      * @return The attribute's type, or null if there is no
    247      *         matching attribute.
    248      * @see org.xml.sax.Attributes#getType(java.lang.String,java.lang.String)
    249      */
    250     public String getType (String uri, String localName)
    251     {
    252 	int max = length * 5;
    253 	for (int i = 0; i < max; i += 5) {
    254 	    if (data[i].equals(uri) && data[i+1].equals(localName)) {
    255 		return data[i+3];
    256 	    }
    257 	}
    258 	return null;
    259     }
    260 
    261 
    262     /**
    263      * Look up an attribute's type by qualified (prefixed) name.
    264      *
    265      * @param qName The qualified name.
    266      * @return The attribute's type, or null if there is no
    267      *         matching attribute.
    268      * @see org.xml.sax.Attributes#getType(java.lang.String)
    269      */
    270     public String getType (String qName)
    271     {
    272 	int max = length * 5;
    273 	for (int i = 0; i < max; i += 5) {
    274 	    if (data[i+2].equals(qName)) {
    275 		return data[i+3];
    276 	    }
    277 	}
    278 	return null;
    279     }
    280 
    281 
    282     /**
    283      * Look up an attribute's value by Namespace-qualified name.
    284      *
    285      * @param uri The Namespace URI, or the empty string for a name
    286      *        with no explicit Namespace URI.
    287      * @param localName The local name.
    288      * @return The attribute's value, or null if there is no
    289      *         matching attribute.
    290      * @see org.xml.sax.Attributes#getValue(java.lang.String,java.lang.String)
    291      */
    292     public String getValue (String uri, String localName)
    293     {
    294 	int max = length * 5;
    295 	for (int i = 0; i < max; i += 5) {
    296 	    if (data[i].equals(uri) && data[i+1].equals(localName)) {
    297 		return data[i+4];
    298 	    }
    299 	}
    300 	return null;
    301     }
    302 
    303 
    304     /**
    305      * Look up an attribute's value by qualified (prefixed) name.
    306      *
    307      * @param qName The qualified name.
    308      * @return The attribute's value, or null if there is no
    309      *         matching attribute.
    310      * @see org.xml.sax.Attributes#getValue(java.lang.String)
    311      */
    312     public String getValue (String qName)
    313     {
    314 	int max = length * 5;
    315 	for (int i = 0; i < max; i += 5) {
    316 	    if (data[i+2].equals(qName)) {
    317 		return data[i+4];
    318 	    }
    319 	}
    320 	return null;
    321     }
    322 
    323 
    324 
    325     ////////////////////////////////////////////////////////////////////
    327     // Manipulators.
    328     ////////////////////////////////////////////////////////////////////
    329 
    330 
    331     /**
    332      * Clear the attribute list for reuse.
    333      *
    334      * <p>Note that little memory is freed by this call:
    335      * the current array is kept so it can be
    336      * reused.</p>
    337      */
    338     public void clear ()
    339     {
    340 	if (data != null) {
    341 	    for (int i = 0; i < (length * 5); i++)
    342 		data [i] = null;
    343 	}
    344 	length = 0;
    345     }
    346 
    347 
    348     /**
    349      * Copy an entire Attributes object.
    350      *
    351      * <p>It may be more efficient to reuse an existing object
    352      * rather than constantly allocating new ones.</p>
    353      *
    354      * @param atts The attributes to copy.
    355      */
    356     public void setAttributes (Attributes atts)
    357     {
    358         clear();
    359         length = atts.getLength();
    360         if (length > 0) {
    361             data = new String[length*5];
    362             for (int i = 0; i < length; i++) {
    363                 data[i*5] = atts.getURI(i);
    364                 data[i*5+1] = atts.getLocalName(i);
    365                 data[i*5+2] = atts.getQName(i);
    366                 data[i*5+3] = atts.getType(i);
    367                 data[i*5+4] = atts.getValue(i);
    368             }
    369 	}
    370     }
    371 
    372 
    373     /**
    374      * Add an attribute to the end of the list.
    375      *
    376      * <p>For the sake of speed, this method does no checking
    377      * to see if the attribute is already in the list: that is
    378      * the responsibility of the application.</p>
    379      *
    380      * @param uri The Namespace URI, or the empty string if
    381      *        none is available or Namespace processing is not
    382      *        being performed.
    383      * @param localName The local name, or the empty string if
    384      *        Namespace processing is not being performed.
    385      * @param qName The qualified (prefixed) name, or the empty string
    386      *        if qualified names are not available.
    387      * @param type The attribute type as a string.
    388      * @param value The attribute value.
    389      */
    390     public void addAttribute (String uri, String localName, String qName,
    391 			      String type, String value)
    392     {
    393 	ensureCapacity(length+1);
    394 	data[length*5] = uri;
    395 	data[length*5+1] = localName;
    396 	data[length*5+2] = qName;
    397 	data[length*5+3] = type;
    398 	data[length*5+4] = value;
    399 	length++;
    400     }
    401 
    402 
    403     /**
    404      * Set an attribute in the list.
    405      *
    406      * <p>For the sake of speed, this method does no checking
    407      * for name conflicts or well-formedness: such checks are the
    408      * responsibility of the application.</p>
    409      *
    410      * @param index The index of the attribute (zero-based).
    411      * @param uri The Namespace URI, or the empty string if
    412      *        none is available or Namespace processing is not
    413      *        being performed.
    414      * @param localName The local name, or the empty string if
    415      *        Namespace processing is not being performed.
    416      * @param qName The qualified name, or the empty string
    417      *        if qualified names are not available.
    418      * @param type The attribute type as a string.
    419      * @param value The attribute value.
    420      * @exception java.lang.ArrayIndexOutOfBoundsException When the
    421      *            supplied index does not point to an attribute
    422      *            in the list.
    423      */
    424     public void setAttribute (int index, String uri, String localName,
    425 			      String qName, String type, String value)
    426     {
    427 	if (index >= 0 && index < length) {
    428 	    data[index*5] = uri;
    429 	    data[index*5+1] = localName;
    430 	    data[index*5+2] = qName;
    431 	    data[index*5+3] = type;
    432 	    data[index*5+4] = value;
    433 	} else {
    434 	    badIndex(index);
    435 	}
    436     }
    437 
    438 
    439     /**
    440      * Remove an attribute from the list.
    441      *
    442      * @param index The index of the attribute (zero-based).
    443      * @exception java.lang.ArrayIndexOutOfBoundsException When the
    444      *            supplied index does not point to an attribute
    445      *            in the list.
    446      */
    447     public void removeAttribute (int index)
    448     {
    449 	if (index >= 0 && index < length) {
    450 	    if (index < length - 1) {
    451 		System.arraycopy(data, (index+1)*5, data, index*5,
    452 				 (length-index-1)*5);
    453 	    }
    454 	    index = (length - 1) * 5;
    455 	    data [index++] = null;
    456 	    data [index++] = null;
    457 	    data [index++] = null;
    458 	    data [index++] = null;
    459 	    data [index] = null;
    460 	    length--;
    461 	} else {
    462 	    badIndex(index);
    463 	}
    464     }
    465 
    466 
    467     /**
    468      * Set the Namespace URI of a specific attribute.
    469      *
    470      * @param index The index of the attribute (zero-based).
    471      * @param uri The attribute's Namespace URI, or the empty
    472      *        string for none.
    473      * @exception java.lang.ArrayIndexOutOfBoundsException When the
    474      *            supplied index does not point to an attribute
    475      *            in the list.
    476      */
    477     public void setURI (int index, String uri)
    478     {
    479 	if (index >= 0 && index < length) {
    480 	    data[index*5] = uri;
    481 	} else {
    482 	    badIndex(index);
    483 	}
    484     }
    485 
    486 
    487     /**
    488      * Set the local name of a specific attribute.
    489      *
    490      * @param index The index of the attribute (zero-based).
    491      * @param localName The attribute's local name, or the empty
    492      *        string for none.
    493      * @exception java.lang.ArrayIndexOutOfBoundsException When the
    494      *            supplied index does not point to an attribute
    495      *            in the list.
    496      */
    497     public void setLocalName (int index, String localName)
    498     {
    499 	if (index >= 0 && index < length) {
    500 	    data[index*5+1] = localName;
    501 	} else {
    502 	    badIndex(index);
    503 	}
    504     }
    505 
    506 
    507     /**
    508      * Set the qualified name of a specific attribute.
    509      *
    510      * @param index The index of the attribute (zero-based).
    511      * @param qName The attribute's qualified name, or the empty
    512      *        string for none.
    513      * @exception java.lang.ArrayIndexOutOfBoundsException When the
    514      *            supplied index does not point to an attribute
    515      *            in the list.
    516      */
    517     public void setQName (int index, String qName)
    518     {
    519 	if (index >= 0 && index < length) {
    520 	    data[index*5+2] = qName;
    521 	} else {
    522 	    badIndex(index);
    523 	}
    524     }
    525 
    526 
    527     /**
    528      * Set the type of a specific attribute.
    529      *
    530      * @param index The index of the attribute (zero-based).
    531      * @param type The attribute's type.
    532      * @exception java.lang.ArrayIndexOutOfBoundsException When the
    533      *            supplied index does not point to an attribute
    534      *            in the list.
    535      */
    536     public void setType (int index, String type)
    537     {
    538 	if (index >= 0 && index < length) {
    539 	    data[index*5+3] = type;
    540 	} else {
    541 	    badIndex(index);
    542 	}
    543     }
    544 
    545 
    546     /**
    547      * Set the value of a specific attribute.
    548      *
    549      * @param index The index of the attribute (zero-based).
    550      * @param value The attribute's value.
    551      * @exception java.lang.ArrayIndexOutOfBoundsException When the
    552      *            supplied index does not point to an attribute
    553      *            in the list.
    554      */
    555     public void setValue (int index, String value)
    556     {
    557 	if (index >= 0 && index < length) {
    558 	    data[index*5+4] = value;
    559 	} else {
    560 	    badIndex(index);
    561 	}
    562     }
    563 
    564 
    565 
    566     ////////////////////////////////////////////////////////////////////
    568     // Internal methods.
    569     ////////////////////////////////////////////////////////////////////
    570 
    571 
    572     /**
    573      * Ensure the internal array's capacity.
    574      *
    575      * @param n The minimum number of attributes that the array must
    576      *        be able to hold.
    577      */
    578     private void ensureCapacity (int n)    {
    579         if (n <= 0) {
    580             return;
    581         }
    582         int max;
    583         if (data == null || data.length == 0) {
    584             max = 25;
    585         }
    586         else if (data.length >= n * 5) {
    587             return;
    588         }
    589         else {
    590             max = data.length;
    591         }
    592         while (max < n * 5) {
    593             max *= 2;
    594         }
    595 
    596         String newData[] = new String[max];
    597         if (length > 0) {
    598             System.arraycopy(data, 0, newData, 0, length*5);
    599         }
    600         data = newData;
    601     }
    602 
    603 
    604     /**
    605      * Report a bad array index in a manipulator.
    606      *
    607      * @param index The index to report.
    608      * @exception java.lang.ArrayIndexOutOfBoundsException Always.
    609      */
    610     private void badIndex (int index)
    611 	throws ArrayIndexOutOfBoundsException
    612     {
    613 	String msg =
    614 	    "Attempt to modify attribute at illegal index: " + index;
    615 	throw new ArrayIndexOutOfBoundsException(msg);
    616     }
    617 
    618 
    619 
    620     ////////////////////////////////////////////////////////////////////
    622     // Internal state.
    623     ////////////////////////////////////////////////////////////////////
    624 
    625     int length;
    626     String data [];
    627 
    628 }
    629 
    630 // end of AttributesImpl.java
    631 
    632