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