1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/parser/html/javasrc/HtmlAttributes.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,598 @@ 1.4 +/* 1.5 + * Copyright (c) 2007 Henri Sivonen 1.6 + * Copyright (c) 2008-2011 Mozilla Foundation 1.7 + * 1.8 + * Permission is hereby granted, free of charge, to any person obtaining a 1.9 + * copy of this software and associated documentation files (the "Software"), 1.10 + * to deal in the Software without restriction, including without limitation 1.11 + * the rights to use, copy, modify, merge, publish, distribute, sublicense, 1.12 + * and/or sell copies of the Software, and to permit persons to whom the 1.13 + * Software is furnished to do so, subject to the following conditions: 1.14 + * 1.15 + * The above copyright notice and this permission notice shall be included in 1.16 + * all copies or substantial portions of the Software. 1.17 + * 1.18 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1.19 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1.20 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 1.21 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1.22 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 1.23 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 1.24 + * DEALINGS IN THE SOFTWARE. 1.25 + */ 1.26 + 1.27 +package nu.validator.htmlparser.impl; 1.28 + 1.29 +import nu.validator.htmlparser.annotation.Auto; 1.30 +import nu.validator.htmlparser.annotation.IdType; 1.31 +import nu.validator.htmlparser.annotation.Local; 1.32 +import nu.validator.htmlparser.annotation.NsUri; 1.33 +import nu.validator.htmlparser.annotation.Prefix; 1.34 +import nu.validator.htmlparser.annotation.QName; 1.35 +import nu.validator.htmlparser.common.Interner; 1.36 +import nu.validator.htmlparser.common.XmlViolationPolicy; 1.37 + 1.38 +import org.xml.sax.Attributes; 1.39 +import org.xml.sax.SAXException; 1.40 + 1.41 +/** 1.42 + * Be careful with this class. QName is the name in from HTML tokenization. 1.43 + * Otherwise, please refer to the interface doc. 1.44 + * 1.45 + * @version $Id: AttributesImpl.java 206 2008-03-20 14:09:29Z hsivonen $ 1.46 + * @author hsivonen 1.47 + */ 1.48 +public final class HtmlAttributes implements Attributes { 1.49 + 1.50 + // [NOCPP[ 1.51 + 1.52 + private static final AttributeName[] EMPTY_ATTRIBUTENAMES = new AttributeName[0]; 1.53 + 1.54 + private static final String[] EMPTY_STRINGS = new String[0]; 1.55 + 1.56 + // ]NOCPP] 1.57 + 1.58 + public static final HtmlAttributes EMPTY_ATTRIBUTES = new HtmlAttributes( 1.59 + AttributeName.HTML); 1.60 + 1.61 + private int mode; 1.62 + 1.63 + private int length; 1.64 + 1.65 + private @Auto AttributeName[] names; 1.66 + 1.67 + private @Auto String[] values; // XXX perhaps make this @NoLength? 1.68 + 1.69 + // [NOCPP[ 1.70 + 1.71 + private String idValue; 1.72 + 1.73 + private int xmlnsLength; 1.74 + 1.75 + private AttributeName[] xmlnsNames; 1.76 + 1.77 + private String[] xmlnsValues; 1.78 + 1.79 + // ]NOCPP] 1.80 + 1.81 + public HtmlAttributes(int mode) { 1.82 + this.mode = mode; 1.83 + this.length = 0; 1.84 + /* 1.85 + * The length of 5 covers covers 98.3% of elements 1.86 + * according to Hixie 1.87 + */ 1.88 + this.names = new AttributeName[5]; 1.89 + this.values = new String[5]; 1.90 + 1.91 + // [NOCPP[ 1.92 + 1.93 + this.idValue = null; 1.94 + 1.95 + this.xmlnsLength = 0; 1.96 + 1.97 + this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES; 1.98 + 1.99 + this.xmlnsValues = HtmlAttributes.EMPTY_STRINGS; 1.100 + 1.101 + // ]NOCPP] 1.102 + } 1.103 + /* 1.104 + public HtmlAttributes(HtmlAttributes other) { 1.105 + this.mode = other.mode; 1.106 + this.length = other.length; 1.107 + this.names = new AttributeName[other.length]; 1.108 + this.values = new String[other.length]; 1.109 + // [NOCPP[ 1.110 + this.idValue = other.idValue; 1.111 + this.xmlnsLength = other.xmlnsLength; 1.112 + this.xmlnsNames = new AttributeName[other.xmlnsLength]; 1.113 + this.xmlnsValues = new String[other.xmlnsLength]; 1.114 + // ]NOCPP] 1.115 + } 1.116 + */ 1.117 + 1.118 + void destructor() { 1.119 + clear(0); 1.120 + } 1.121 + 1.122 + /** 1.123 + * Only use with a static argument 1.124 + * 1.125 + * @param name 1.126 + * @return 1.127 + */ 1.128 + public int getIndex(AttributeName name) { 1.129 + for (int i = 0; i < length; i++) { 1.130 + if (names[i] == name) { 1.131 + return i; 1.132 + } 1.133 + } 1.134 + return -1; 1.135 + } 1.136 + 1.137 + /** 1.138 + * Only use with static argument. 1.139 + * 1.140 + * @see org.xml.sax.Attributes#getValue(java.lang.String) 1.141 + */ 1.142 + public String getValue(AttributeName name) { 1.143 + int index = getIndex(name); 1.144 + if (index == -1) { 1.145 + return null; 1.146 + } else { 1.147 + return getValueNoBoundsCheck(index); 1.148 + } 1.149 + } 1.150 + 1.151 + public int getLength() { 1.152 + return length; 1.153 + } 1.154 + 1.155 + /** 1.156 + * Variant of <code>getLocalName(int index)</code> without bounds check. 1.157 + * @param index a valid attribute index 1.158 + * @return the local name at index 1.159 + */ 1.160 + public @Local String getLocalNameNoBoundsCheck(int index) { 1.161 + // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; 1.162 + return names[index].getLocal(mode); 1.163 + } 1.164 + 1.165 + /** 1.166 + * Variant of <code>getURI(int index)</code> without bounds check. 1.167 + * @param index a valid attribute index 1.168 + * @return the namespace URI at index 1.169 + */ 1.170 + public @NsUri String getURINoBoundsCheck(int index) { 1.171 + // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; 1.172 + return names[index].getUri(mode); 1.173 + } 1.174 + 1.175 + /** 1.176 + * Variant of <code>getPrefix(int index)</code> without bounds check. 1.177 + * @param index a valid attribute index 1.178 + * @return the namespace prefix at index 1.179 + */ 1.180 + public @Prefix String getPrefixNoBoundsCheck(int index) { 1.181 + // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; 1.182 + return names[index].getPrefix(mode); 1.183 + } 1.184 + 1.185 + /** 1.186 + * Variant of <code>getValue(int index)</code> without bounds check. 1.187 + * @param index a valid attribute index 1.188 + * @return the attribute value at index 1.189 + */ 1.190 + public String getValueNoBoundsCheck(int index) { 1.191 + // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; 1.192 + return values[index]; 1.193 + } 1.194 + 1.195 + /** 1.196 + * Variant of <code>getAttributeName(int index)</code> without bounds check. 1.197 + * @param index a valid attribute index 1.198 + * @return the attribute name at index 1.199 + */ 1.200 + public AttributeName getAttributeNameNoBoundsCheck(int index) { 1.201 + // CPPONLY: assert index < length && index >= 0: "Index out of bounds"; 1.202 + return names[index]; 1.203 + } 1.204 + 1.205 + // [NOCPP[ 1.206 + 1.207 + /** 1.208 + * Variant of <code>getQName(int index)</code> without bounds check. 1.209 + * @param index a valid attribute index 1.210 + * @return the QName at index 1.211 + */ 1.212 + public @QName String getQNameNoBoundsCheck(int index) { 1.213 + return names[index].getQName(mode); 1.214 + } 1.215 + 1.216 + /** 1.217 + * Variant of <code>getType(int index)</code> without bounds check. 1.218 + * @param index a valid attribute index 1.219 + * @return the attribute type at index 1.220 + */ 1.221 + public @IdType String getTypeNoBoundsCheck(int index) { 1.222 + return (names[index] == AttributeName.ID) ? "ID" : "CDATA"; 1.223 + } 1.224 + 1.225 + public int getIndex(String qName) { 1.226 + for (int i = 0; i < length; i++) { 1.227 + if (names[i].getQName(mode).equals(qName)) { 1.228 + return i; 1.229 + } 1.230 + } 1.231 + return -1; 1.232 + } 1.233 + 1.234 + public int getIndex(String uri, String localName) { 1.235 + for (int i = 0; i < length; i++) { 1.236 + if (names[i].getLocal(mode).equals(localName) 1.237 + && names[i].getUri(mode).equals(uri)) { 1.238 + return i; 1.239 + } 1.240 + } 1.241 + return -1; 1.242 + } 1.243 + 1.244 + public @IdType String getType(String qName) { 1.245 + int index = getIndex(qName); 1.246 + if (index == -1) { 1.247 + return null; 1.248 + } else { 1.249 + return getType(index); 1.250 + } 1.251 + } 1.252 + 1.253 + public @IdType String getType(String uri, String localName) { 1.254 + int index = getIndex(uri, localName); 1.255 + if (index == -1) { 1.256 + return null; 1.257 + } else { 1.258 + return getType(index); 1.259 + } 1.260 + } 1.261 + 1.262 + public String getValue(String qName) { 1.263 + int index = getIndex(qName); 1.264 + if (index == -1) { 1.265 + return null; 1.266 + } else { 1.267 + return getValue(index); 1.268 + } 1.269 + } 1.270 + 1.271 + public String getValue(String uri, String localName) { 1.272 + int index = getIndex(uri, localName); 1.273 + if (index == -1) { 1.274 + return null; 1.275 + } else { 1.276 + return getValue(index); 1.277 + } 1.278 + } 1.279 + 1.280 + public @Local String getLocalName(int index) { 1.281 + if (index < length && index >= 0) { 1.282 + return names[index].getLocal(mode); 1.283 + } else { 1.284 + return null; 1.285 + } 1.286 + } 1.287 + 1.288 + public @QName String getQName(int index) { 1.289 + if (index < length && index >= 0) { 1.290 + return names[index].getQName(mode); 1.291 + } else { 1.292 + return null; 1.293 + } 1.294 + } 1.295 + 1.296 + public @IdType String getType(int index) { 1.297 + if (index < length && index >= 0) { 1.298 + return (names[index] == AttributeName.ID) ? "ID" : "CDATA"; 1.299 + } else { 1.300 + return null; 1.301 + } 1.302 + } 1.303 + 1.304 + public AttributeName getAttributeName(int index) { 1.305 + if (index < length && index >= 0) { 1.306 + return names[index]; 1.307 + } else { 1.308 + return null; 1.309 + } 1.310 + } 1.311 + 1.312 + public @NsUri String getURI(int index) { 1.313 + if (index < length && index >= 0) { 1.314 + return names[index].getUri(mode); 1.315 + } else { 1.316 + return null; 1.317 + } 1.318 + } 1.319 + 1.320 + public @Prefix String getPrefix(int index) { 1.321 + if (index < length && index >= 0) { 1.322 + return names[index].getPrefix(mode); 1.323 + } else { 1.324 + return null; 1.325 + } 1.326 + } 1.327 + 1.328 + public String getValue(int index) { 1.329 + if (index < length && index >= 0) { 1.330 + return values[index]; 1.331 + } else { 1.332 + return null; 1.333 + } 1.334 + } 1.335 + 1.336 + public String getId() { 1.337 + return idValue; 1.338 + } 1.339 + 1.340 + public int getXmlnsLength() { 1.341 + return xmlnsLength; 1.342 + } 1.343 + 1.344 + public @Local String getXmlnsLocalName(int index) { 1.345 + if (index < xmlnsLength && index >= 0) { 1.346 + return xmlnsNames[index].getLocal(mode); 1.347 + } else { 1.348 + return null; 1.349 + } 1.350 + } 1.351 + 1.352 + public @NsUri String getXmlnsURI(int index) { 1.353 + if (index < xmlnsLength && index >= 0) { 1.354 + return xmlnsNames[index].getUri(mode); 1.355 + } else { 1.356 + return null; 1.357 + } 1.358 + } 1.359 + 1.360 + public String getXmlnsValue(int index) { 1.361 + if (index < xmlnsLength && index >= 0) { 1.362 + return xmlnsValues[index]; 1.363 + } else { 1.364 + return null; 1.365 + } 1.366 + } 1.367 + 1.368 + public int getXmlnsIndex(AttributeName name) { 1.369 + for (int i = 0; i < xmlnsLength; i++) { 1.370 + if (xmlnsNames[i] == name) { 1.371 + return i; 1.372 + } 1.373 + } 1.374 + return -1; 1.375 + } 1.376 + 1.377 + public String getXmlnsValue(AttributeName name) { 1.378 + int index = getXmlnsIndex(name); 1.379 + if (index == -1) { 1.380 + return null; 1.381 + } else { 1.382 + return getXmlnsValue(index); 1.383 + } 1.384 + } 1.385 + 1.386 + public AttributeName getXmlnsAttributeName(int index) { 1.387 + if (index < xmlnsLength && index >= 0) { 1.388 + return xmlnsNames[index]; 1.389 + } else { 1.390 + return null; 1.391 + } 1.392 + } 1.393 + 1.394 + // ]NOCPP] 1.395 + 1.396 + void addAttribute(AttributeName name, String value 1.397 + // [NOCPP[ 1.398 + , XmlViolationPolicy xmlnsPolicy 1.399 + // ]NOCPP] 1.400 + ) throws SAXException { 1.401 + // [NOCPP[ 1.402 + if (name == AttributeName.ID) { 1.403 + idValue = value; 1.404 + } 1.405 + 1.406 + if (name.isXmlns()) { 1.407 + if (xmlnsNames.length == xmlnsLength) { 1.408 + int newLen = xmlnsLength == 0 ? 2 : xmlnsLength << 1; 1.409 + AttributeName[] newNames = new AttributeName[newLen]; 1.410 + System.arraycopy(xmlnsNames, 0, newNames, 0, xmlnsNames.length); 1.411 + xmlnsNames = newNames; 1.412 + String[] newValues = new String[newLen]; 1.413 + System.arraycopy(xmlnsValues, 0, newValues, 0, xmlnsValues.length); 1.414 + xmlnsValues = newValues; 1.415 + } 1.416 + xmlnsNames[xmlnsLength] = name; 1.417 + xmlnsValues[xmlnsLength] = value; 1.418 + xmlnsLength++; 1.419 + switch (xmlnsPolicy) { 1.420 + case FATAL: 1.421 + // this is ugly 1.422 + throw new SAXException("Saw an xmlns attribute."); 1.423 + case ALTER_INFOSET: 1.424 + return; 1.425 + case ALLOW: 1.426 + // fall through 1.427 + } 1.428 + } 1.429 + 1.430 + // ]NOCPP] 1.431 + 1.432 + if (names.length == length) { 1.433 + int newLen = length << 1; // The first growth covers virtually 1.434 + // 100% of elements according to 1.435 + // Hixie 1.436 + AttributeName[] newNames = new AttributeName[newLen]; 1.437 + System.arraycopy(names, 0, newNames, 0, names.length); 1.438 + names = newNames; 1.439 + String[] newValues = new String[newLen]; 1.440 + System.arraycopy(values, 0, newValues, 0, values.length); 1.441 + values = newValues; 1.442 + } 1.443 + names[length] = name; 1.444 + values[length] = value; 1.445 + length++; 1.446 + } 1.447 + 1.448 + void clear(int m) { 1.449 + for (int i = 0; i < length; i++) { 1.450 + names[i].release(); 1.451 + names[i] = null; 1.452 + Portability.releaseString(values[i]); 1.453 + values[i] = null; 1.454 + } 1.455 + length = 0; 1.456 + mode = m; 1.457 + // [NOCPP[ 1.458 + idValue = null; 1.459 + for (int i = 0; i < xmlnsLength; i++) { 1.460 + xmlnsNames[i] = null; 1.461 + xmlnsValues[i] = null; 1.462 + } 1.463 + xmlnsLength = 0; 1.464 + // ]NOCPP] 1.465 + } 1.466 + 1.467 + /** 1.468 + * This is used in C++ to release special <code>isindex</code> 1.469 + * attribute values whose ownership is not transferred. 1.470 + */ 1.471 + void releaseValue(int i) { 1.472 + Portability.releaseString(values[i]); 1.473 + } 1.474 + 1.475 + /** 1.476 + * This is only used for <code>AttributeName</code> ownership transfer 1.477 + * in the isindex case to avoid freeing custom names twice in C++. 1.478 + */ 1.479 + void clearWithoutReleasingContents() { 1.480 + for (int i = 0; i < length; i++) { 1.481 + names[i] = null; 1.482 + values[i] = null; 1.483 + } 1.484 + length = 0; 1.485 + } 1.486 + 1.487 + boolean contains(AttributeName name) { 1.488 + for (int i = 0; i < length; i++) { 1.489 + if (name.equalsAnother(names[i])) { 1.490 + return true; 1.491 + } 1.492 + } 1.493 + // [NOCPP[ 1.494 + for (int i = 0; i < xmlnsLength; i++) { 1.495 + if (name.equalsAnother(xmlnsNames[i])) { 1.496 + return true; 1.497 + } 1.498 + } 1.499 + // ]NOCPP] 1.500 + return false; 1.501 + } 1.502 + 1.503 + public void adjustForMath() { 1.504 + mode = AttributeName.MATHML; 1.505 + } 1.506 + 1.507 + public void adjustForSvg() { 1.508 + mode = AttributeName.SVG; 1.509 + } 1.510 + 1.511 + public HtmlAttributes cloneAttributes(Interner interner) 1.512 + throws SAXException { 1.513 + assert (length == 0 1.514 + // [NOCPP[ 1.515 + && xmlnsLength == 0 1.516 + // ]NOCPP] 1.517 + ) 1.518 + || mode == 0 || mode == 3; 1.519 + HtmlAttributes clone = new HtmlAttributes(0); 1.520 + for (int i = 0; i < length; i++) { 1.521 + clone.addAttribute(names[i].cloneAttributeName(interner), 1.522 + Portability.newStringFromString(values[i]) 1.523 + // [NOCPP[ 1.524 + , XmlViolationPolicy.ALLOW 1.525 + // ]NOCPP] 1.526 + ); 1.527 + } 1.528 + // [NOCPP[ 1.529 + for (int i = 0; i < xmlnsLength; i++) { 1.530 + clone.addAttribute(xmlnsNames[i], xmlnsValues[i], 1.531 + XmlViolationPolicy.ALLOW); 1.532 + } 1.533 + // ]NOCPP] 1.534 + return clone; // XXX!!! 1.535 + } 1.536 + 1.537 + public boolean equalsAnother(HtmlAttributes other) { 1.538 + assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content."; 1.539 + int otherLength = other.getLength(); 1.540 + if (length != otherLength) { 1.541 + return false; 1.542 + } 1.543 + for (int i = 0; i < length; i++) { 1.544 + // Work around the limitations of C++ 1.545 + boolean found = false; 1.546 + // The comparing just the local names is OK, since these attribute 1.547 + // holders are both supposed to belong to HTML formatting elements 1.548 + @Local String ownLocal = names[i].getLocal(AttributeName.HTML); 1.549 + for (int j = 0; j < otherLength; j++) { 1.550 + if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) { 1.551 + found = true; 1.552 + if (!Portability.stringEqualsString(values[i], other.values[j])) { 1.553 + return false; 1.554 + } 1.555 + } 1.556 + } 1.557 + if (!found) { 1.558 + return false; 1.559 + } 1.560 + } 1.561 + return true; 1.562 + } 1.563 + 1.564 + // [NOCPP[ 1.565 + 1.566 + void processNonNcNames(TreeBuilder<?> treeBuilder, XmlViolationPolicy namePolicy) throws SAXException { 1.567 + for (int i = 0; i < length; i++) { 1.568 + AttributeName attName = names[i]; 1.569 + if (!attName.isNcName(mode)) { 1.570 + String name = attName.getLocal(mode); 1.571 + switch (namePolicy) { 1.572 + case ALTER_INFOSET: 1.573 + names[i] = AttributeName.create(NCName.escapeName(name)); 1.574 + // fall through 1.575 + case ALLOW: 1.576 + if (attName != AttributeName.XML_LANG) { 1.577 + treeBuilder.warn("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0."); 1.578 + } 1.579 + break; 1.580 + case FATAL: 1.581 + treeBuilder.fatal("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0."); 1.582 + break; 1.583 + } 1.584 + } 1.585 + } 1.586 + } 1.587 + 1.588 + public void merge(HtmlAttributes attributes) throws SAXException { 1.589 + int len = attributes.getLength(); 1.590 + for (int i = 0; i < len; i++) { 1.591 + AttributeName name = attributes.getAttributeNameNoBoundsCheck(i); 1.592 + if (!contains(name)) { 1.593 + addAttribute(name, attributes.getValueNoBoundsCheck(i), XmlViolationPolicy.ALLOW); 1.594 + } 1.595 + } 1.596 + } 1.597 + 1.598 + 1.599 + // ]NOCPP] 1.600 + 1.601 +}