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