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