001 /* 002 * Copyright (c) 2007 Henri Sivonen 003 * Copyright (c) 2008-2009 Mozilla Foundation 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining a 006 * copy of this software and associated documentation files (the "Software"), 007 * to deal in the Software without restriction, including without limitation 008 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 009 * and/or sell copies of the Software, and to permit persons to whom the 010 * Software is furnished to do so, subject to the following conditions: 011 * 012 * The above copyright notice and this permission notice shall be included in 013 * all copies or substantial portions of the Software. 014 * 015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 018 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 020 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 021 * DEALINGS IN THE SOFTWARE. 022 */ 023 024 package nu.validator.htmlparser.impl; 025 026 import nu.validator.htmlparser.annotation.Auto; 027 import nu.validator.htmlparser.annotation.IdType; 028 import nu.validator.htmlparser.annotation.Local; 029 import nu.validator.htmlparser.annotation.NsUri; 030 import nu.validator.htmlparser.annotation.Prefix; 031 import nu.validator.htmlparser.annotation.QName; 032 import nu.validator.htmlparser.common.Interner; 033 import nu.validator.htmlparser.common.XmlViolationPolicy; 034 035 import org.xml.sax.Attributes; 036 import org.xml.sax.SAXException; 037 038 /** 039 * Be careful with this class. QName is the name in from HTML tokenization. 040 * Otherwise, please refer to the interface doc. 041 * 042 * @version $Id: AttributesImpl.java 206 2008-03-20 14:09:29Z hsivonen $ 043 * @author hsivonen 044 */ 045 public final class HtmlAttributes implements Attributes { 046 047 // [NOCPP[ 048 049 private static final AttributeName[] EMPTY_ATTRIBUTENAMES = new AttributeName[0]; 050 051 private static final String[] EMPTY_STRINGS = new String[0]; 052 053 // ]NOCPP] 054 055 public static final HtmlAttributes EMPTY_ATTRIBUTES = new HtmlAttributes( 056 AttributeName.HTML); 057 058 private int mode; 059 060 private int length; 061 062 private @Auto AttributeName[] names; 063 064 private @Auto String[] values; // XXX perhaps make this @NoLength? 065 066 // [NOCPP[ 067 068 private String idValue; 069 070 private int xmlnsLength; 071 072 private AttributeName[] xmlnsNames; 073 074 private String[] xmlnsValues; 075 076 // ]NOCPP] 077 078 public HtmlAttributes(int mode) { 079 this.mode = mode; 080 this.length = 0; 081 /* 082 * The length of 5 covers covers 98.3% of elements 083 * according to Hixie 084 */ 085 this.names = new AttributeName[5]; 086 this.values = new String[5]; 087 088 // [NOCPP[ 089 090 this.idValue = null; 091 092 this.xmlnsLength = 0; 093 094 this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES; 095 096 this.xmlnsValues = HtmlAttributes.EMPTY_STRINGS; 097 098 // ]NOCPP] 099 } 100 /* 101 public HtmlAttributes(HtmlAttributes other) { 102 this.mode = other.mode; 103 this.length = other.length; 104 this.names = new AttributeName[other.length]; 105 this.values = new String[other.length]; 106 // [NOCPP[ 107 this.idValue = other.idValue; 108 this.xmlnsLength = other.xmlnsLength; 109 this.xmlnsNames = new AttributeName[other.xmlnsLength]; 110 this.xmlnsValues = new String[other.xmlnsLength]; 111 // ]NOCPP] 112 } 113 */ 114 115 void destructor() { 116 clear(0); 117 } 118 119 /** 120 * Only use with a static argument 121 * 122 * @param name 123 * @return 124 */ 125 public int getIndex(AttributeName name) { 126 for (int i = 0; i < length; i++) { 127 if (names[i] == name) { 128 return i; 129 } 130 } 131 return -1; 132 } 133 134 // [NOCPP[ 135 136 public int getIndex(String qName) { 137 for (int i = 0; i < length; i++) { 138 if (names[i].getQName(mode).equals(qName)) { 139 return i; 140 } 141 } 142 return -1; 143 } 144 145 public int getIndex(String uri, String localName) { 146 for (int i = 0; i < length; i++) { 147 if (names[i].getLocal(mode).equals(localName) 148 && names[i].getUri(mode).equals(uri)) { 149 return i; 150 } 151 } 152 return -1; 153 } 154 155 public @IdType String getType(String qName) { 156 int index = getIndex(qName); 157 if (index == -1) { 158 return null; 159 } else { 160 return getType(index); 161 } 162 } 163 164 public @IdType String getType(String uri, String localName) { 165 int index = getIndex(uri, localName); 166 if (index == -1) { 167 return null; 168 } else { 169 return getType(index); 170 } 171 } 172 173 public String getValue(String qName) { 174 int index = getIndex(qName); 175 if (index == -1) { 176 return null; 177 } else { 178 return getValue(index); 179 } 180 } 181 182 public String getValue(String uri, String localName) { 183 int index = getIndex(uri, localName); 184 if (index == -1) { 185 return null; 186 } else { 187 return getValue(index); 188 } 189 } 190 191 // ]NOCPP] 192 193 public int getLength() { 194 return length; 195 } 196 197 public @Local String getLocalName(int index) { 198 if (index < length && index >= 0) { 199 return names[index].getLocal(mode); 200 } else { 201 return null; 202 } 203 } 204 205 // [NOCPP[ 206 207 public @QName String getQName(int index) { 208 if (index < length && index >= 0) { 209 return names[index].getQName(mode); 210 } else { 211 return null; 212 } 213 } 214 215 public @IdType String getType(int index) { 216 if (index < length && index >= 0) { 217 return names[index].getType(mode); 218 } else { 219 return null; 220 } 221 } 222 223 // ]NOCPP] 224 225 public AttributeName getAttributeName(int index) { 226 if (index < length && index >= 0) { 227 return names[index]; 228 } else { 229 return null; 230 } 231 } 232 233 public @NsUri String getURI(int index) { 234 if (index < length && index >= 0) { 235 return names[index].getUri(mode); 236 } else { 237 return null; 238 } 239 } 240 241 public @Prefix String getPrefix(int index) { 242 if (index < length && index >= 0) { 243 return names[index].getPrefix(mode); 244 } else { 245 return null; 246 } 247 } 248 249 public String getValue(int index) { 250 if (index < length && index >= 0) { 251 return values[index]; 252 } else { 253 return null; 254 } 255 } 256 257 /** 258 * Only use with static argument. 259 * 260 * @see org.xml.sax.Attributes#getValue(java.lang.String) 261 */ 262 public String getValue(AttributeName name) { 263 int index = getIndex(name); 264 if (index == -1) { 265 return null; 266 } else { 267 return getValue(index); 268 } 269 } 270 271 // [NOCPP[ 272 273 public String getId() { 274 return idValue; 275 } 276 277 public int getXmlnsLength() { 278 return xmlnsLength; 279 } 280 281 public @Local String getXmlnsLocalName(int index) { 282 if (index < xmlnsLength && index >= 0) { 283 return xmlnsNames[index].getLocal(mode); 284 } else { 285 return null; 286 } 287 } 288 289 public @NsUri String getXmlnsURI(int index) { 290 if (index < xmlnsLength && index >= 0) { 291 return xmlnsNames[index].getUri(mode); 292 } else { 293 return null; 294 } 295 } 296 297 public String getXmlnsValue(int index) { 298 if (index < xmlnsLength && index >= 0) { 299 return xmlnsValues[index]; 300 } else { 301 return null; 302 } 303 } 304 305 public int getXmlnsIndex(AttributeName name) { 306 for (int i = 0; i < xmlnsLength; i++) { 307 if (xmlnsNames[i] == name) { 308 return i; 309 } 310 } 311 return -1; 312 } 313 314 public String getXmlnsValue(AttributeName name) { 315 int index = getXmlnsIndex(name); 316 if (index == -1) { 317 return null; 318 } else { 319 return getXmlnsValue(index); 320 } 321 } 322 323 public AttributeName getXmlnsAttributeName(int index) { 324 if (index < xmlnsLength && index >= 0) { 325 return xmlnsNames[index]; 326 } else { 327 return null; 328 } 329 } 330 331 // ]NOCPP] 332 333 void addAttribute(AttributeName name, String value 334 // [NOCPP[ 335 , XmlViolationPolicy xmlnsPolicy 336 // ]NOCPP] 337 ) throws SAXException { 338 // [NOCPP[ 339 if (name == AttributeName.ID) { 340 idValue = value; 341 } 342 343 if (name.isXmlns()) { 344 if (xmlnsNames.length == xmlnsLength) { 345 int newLen = xmlnsLength == 0 ? 2 : xmlnsLength << 1; 346 AttributeName[] newNames = new AttributeName[newLen]; 347 System.arraycopy(xmlnsNames, 0, newNames, 0, xmlnsNames.length); 348 xmlnsNames = newNames; 349 String[] newValues = new String[newLen]; 350 System.arraycopy(xmlnsValues, 0, newValues, 0, xmlnsValues.length); 351 xmlnsValues = newValues; 352 } 353 xmlnsNames[xmlnsLength] = name; 354 xmlnsValues[xmlnsLength] = value; 355 xmlnsLength++; 356 switch (xmlnsPolicy) { 357 case FATAL: 358 // this is ugly 359 throw new SAXException("Saw an xmlns attribute."); 360 case ALTER_INFOSET: 361 return; 362 case ALLOW: 363 // fall through 364 } 365 } 366 367 // ]NOCPP] 368 369 if (names.length == length) { 370 int newLen = length << 1; // The first growth covers virtually 371 // 100% of elements according to 372 // Hixie 373 AttributeName[] newNames = new AttributeName[newLen]; 374 System.arraycopy(names, 0, newNames, 0, names.length); 375 names = newNames; 376 String[] newValues = new String[newLen]; 377 System.arraycopy(values, 0, newValues, 0, values.length); 378 values = newValues; 379 } 380 names[length] = name; 381 values[length] = value; 382 length++; 383 } 384 385 void clear(int m) { 386 for (int i = 0; i < length; i++) { 387 names[i].release(); 388 names[i] = null; 389 Portability.releaseString(values[i]); 390 values[i] = null; 391 } 392 length = 0; 393 mode = m; 394 // [NOCPP[ 395 idValue = null; 396 for (int i = 0; i < xmlnsLength; i++) { 397 xmlnsNames[i] = null; 398 xmlnsValues[i] = null; 399 } 400 xmlnsLength = 0; 401 // ]NOCPP] 402 } 403 404 /** 405 * This is used in C++ to release special <code>isindex</code> 406 * attribute values whose ownership is not transferred. 407 */ 408 void releaseValue(int i) { 409 Portability.releaseString(values[i]); 410 } 411 412 /** 413 * This is only used for <code>AttributeName</code> ownership transfer 414 * in the isindex case to avoid freeing custom names twice in C++. 415 */ 416 void clearWithoutReleasingContents() { 417 for (int i = 0; i < length; i++) { 418 names[i] = null; 419 values[i] = null; 420 } 421 length = 0; 422 } 423 424 boolean contains(AttributeName name) { 425 for (int i = 0; i < length; i++) { 426 if (name.equalsAnother(names[i])) { 427 return true; 428 } 429 } 430 // [NOCPP[ 431 for (int i = 0; i < xmlnsLength; i++) { 432 if (name.equalsAnother(xmlnsNames[i])) { 433 return true; 434 } 435 } 436 // ]NOCPP] 437 return false; 438 } 439 440 public void adjustForMath() { 441 mode = AttributeName.MATHML; 442 } 443 444 public void adjustForSvg() { 445 mode = AttributeName.SVG; 446 } 447 448 public HtmlAttributes cloneAttributes(Interner interner) throws SAXException { 449 assert (length == 0 && xmlnsLength == 0) || mode == 0 || mode == 3; 450 HtmlAttributes clone = new HtmlAttributes(0); 451 for (int i = 0; i < length; i++) { 452 clone.addAttribute(names[i].cloneAttributeName(interner), Portability.newStringFromString(values[i]) 453 // [NOCPP[ 454 , XmlViolationPolicy.ALLOW 455 // ]NOCPP] 456 ); 457 } 458 // [NOCPP[ 459 for (int i = 0; i < xmlnsLength; i++) { 460 clone.addAttribute(xmlnsNames[i], 461 xmlnsValues[i], XmlViolationPolicy.ALLOW); 462 } 463 // ]NOCPP] 464 return clone; // XXX!!! 465 } 466 467 public boolean equalsAnother(HtmlAttributes other) { 468 assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content."; 469 int otherLength = other.getLength(); 470 if (length != otherLength) { 471 return false; 472 } 473 for (int i = 0; i < length; i++) { 474 // Work around the limitations of C++ 475 boolean found = false; 476 // The comparing just the local names is OK, since these attribute 477 // holders are both supposed to belong to HTML formatting elements 478 @Local String ownLocal = names[i].getLocal(AttributeName.HTML); 479 for (int j = 0; j < otherLength; j++) { 480 if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) { 481 found = true; 482 if (!Portability.stringEqualsString(values[i], other.values[j])) { 483 return false; 484 } 485 } 486 } 487 if (!found) { 488 return false; 489 } 490 } 491 return true; 492 } 493 494 // [NOCPP[ 495 496 void processNonNcNames(TreeBuilder<?> treeBuilder, XmlViolationPolicy namePolicy) throws SAXException { 497 for (int i = 0; i < length; i++) { 498 AttributeName attName = names[i]; 499 if (!attName.isNcName(mode)) { 500 String name = attName.getLocal(mode); 501 switch (namePolicy) { 502 case ALTER_INFOSET: 503 names[i] = AttributeName.create(NCName.escapeName(name)); 504 // fall through 505 case ALLOW: 506 if (attName != AttributeName.XML_LANG) { 507 treeBuilder.warn("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0."); 508 } 509 break; 510 case FATAL: 511 treeBuilder.fatal("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0."); 512 break; 513 } 514 } 515 } 516 } 517 518 public void merge(HtmlAttributes attributes) throws SAXException { 519 int len = attributes.getLength(); 520 for (int i = 0; i < len; i++) { 521 AttributeName name = attributes.getAttributeName(i); 522 if (!contains(name)) { 523 addAttribute(name, attributes.getValue(i), XmlViolationPolicy.ALLOW); 524 } 525 } 526 } 527 528 529 // ]NOCPP] 530 531 }