зеркало из https://github.com/mozilla/gecko-dev.git
619 строки
18 KiB
Java
619 строки
18 KiB
Java
/*
|
|
* Copyright (c) 2007 Henri Sivonen
|
|
* Copyright (c) 2008-2011 Mozilla Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
package nu.validator.htmlparser.impl;
|
|
|
|
import nu.validator.htmlparser.annotation.Auto;
|
|
import nu.validator.htmlparser.annotation.IdType;
|
|
import nu.validator.htmlparser.annotation.Local;
|
|
import nu.validator.htmlparser.annotation.NsUri;
|
|
import nu.validator.htmlparser.annotation.Prefix;
|
|
import nu.validator.htmlparser.annotation.QName;
|
|
import nu.validator.htmlparser.common.Interner;
|
|
import nu.validator.htmlparser.common.XmlViolationPolicy;
|
|
|
|
import org.xml.sax.Attributes;
|
|
import org.xml.sax.SAXException;
|
|
|
|
/**
|
|
* Be careful with this class. QName is the name in from HTML tokenization.
|
|
* Otherwise, please refer to the interface doc.
|
|
*
|
|
* @version $Id: AttributesImpl.java 206 2008-03-20 14:09:29Z hsivonen $
|
|
* @author hsivonen
|
|
*/
|
|
public final class HtmlAttributes implements Attributes {
|
|
|
|
// [NOCPP[
|
|
|
|
private static final AttributeName[] EMPTY_ATTRIBUTENAMES = new AttributeName[0];
|
|
|
|
private static final String[] EMPTY_STRINGS = new String[0];
|
|
|
|
// ]NOCPP]
|
|
|
|
public static final HtmlAttributes EMPTY_ATTRIBUTES = new HtmlAttributes(
|
|
AttributeName.HTML);
|
|
|
|
private int mode;
|
|
|
|
private int length;
|
|
|
|
private @Auto AttributeName[] names;
|
|
|
|
private @Auto String[] values; // XXX perhaps make this @NoLength?
|
|
|
|
// CPPONLY: private @Auto int[] lines; // XXX perhaps make this @NoLength?
|
|
|
|
// [NOCPP[
|
|
|
|
private String idValue;
|
|
|
|
private int xmlnsLength;
|
|
|
|
private AttributeName[] xmlnsNames;
|
|
|
|
private String[] xmlnsValues;
|
|
|
|
// ]NOCPP]
|
|
|
|
public HtmlAttributes(int mode) {
|
|
this.mode = mode;
|
|
this.length = 0;
|
|
/*
|
|
* The length of 5 covers covers 98.3% of elements
|
|
* according to Hixie, but lets round to the next power of two for
|
|
* jemalloc.
|
|
*/
|
|
this.names = new AttributeName[8];
|
|
this.values = new String[8];
|
|
// CPPONLY: this.lines = new int[8];
|
|
|
|
// [NOCPP[
|
|
|
|
this.idValue = null;
|
|
|
|
this.xmlnsLength = 0;
|
|
|
|
this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES;
|
|
|
|
this.xmlnsValues = HtmlAttributes.EMPTY_STRINGS;
|
|
|
|
// ]NOCPP]
|
|
}
|
|
/*
|
|
public HtmlAttributes(HtmlAttributes other) {
|
|
this.mode = other.mode;
|
|
this.length = other.length;
|
|
this.names = new AttributeName[other.length];
|
|
this.values = new String[other.length];
|
|
// [NOCPP[
|
|
this.idValue = other.idValue;
|
|
this.xmlnsLength = other.xmlnsLength;
|
|
this.xmlnsNames = new AttributeName[other.xmlnsLength];
|
|
this.xmlnsValues = new String[other.xmlnsLength];
|
|
// ]NOCPP]
|
|
}
|
|
*/
|
|
|
|
void destructor() {
|
|
clear(0);
|
|
}
|
|
|
|
/**
|
|
* Only use with a static argument
|
|
*
|
|
* @param name
|
|
* @return
|
|
*/
|
|
public int getIndex(AttributeName name) {
|
|
for (int i = 0; i < length; i++) {
|
|
if (names[i] == name) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Only use with static argument.
|
|
*
|
|
* @see org.xml.sax.Attributes#getValue(java.lang.String)
|
|
*/
|
|
public String getValue(AttributeName name) {
|
|
int index = getIndex(name);
|
|
if (index == -1) {
|
|
return null;
|
|
} else {
|
|
return getValueNoBoundsCheck(index);
|
|
}
|
|
}
|
|
|
|
public int getLength() {
|
|
return length;
|
|
}
|
|
|
|
/**
|
|
* Variant of <code>getLocalName(int index)</code> without bounds check.
|
|
* @param index a valid attribute index
|
|
* @return the local name at index
|
|
*/
|
|
public @Local String getLocalNameNoBoundsCheck(int index) {
|
|
// CPPONLY: assert index < length && index >= 0: "Index out of bounds";
|
|
return names[index].getLocal(mode);
|
|
}
|
|
|
|
/**
|
|
* Variant of <code>getURI(int index)</code> without bounds check.
|
|
* @param index a valid attribute index
|
|
* @return the namespace URI at index
|
|
*/
|
|
public @NsUri String getURINoBoundsCheck(int index) {
|
|
// CPPONLY: assert index < length && index >= 0: "Index out of bounds";
|
|
return names[index].getUri(mode);
|
|
}
|
|
|
|
/**
|
|
* Variant of <code>getPrefix(int index)</code> without bounds check.
|
|
* @param index a valid attribute index
|
|
* @return the namespace prefix at index
|
|
*/
|
|
public @Prefix String getPrefixNoBoundsCheck(int index) {
|
|
// CPPONLY: assert index < length && index >= 0: "Index out of bounds";
|
|
return names[index].getPrefix(mode);
|
|
}
|
|
|
|
/**
|
|
* Variant of <code>getValue(int index)</code> without bounds check.
|
|
* @param index a valid attribute index
|
|
* @return the attribute value at index
|
|
*/
|
|
public String getValueNoBoundsCheck(int index) {
|
|
// CPPONLY: assert index < length && index >= 0: "Index out of bounds";
|
|
return values[index];
|
|
}
|
|
|
|
/**
|
|
* Variant of <code>getAttributeName(int index)</code> without bounds check.
|
|
* @param index a valid attribute index
|
|
* @return the attribute name at index
|
|
*/
|
|
public AttributeName getAttributeNameNoBoundsCheck(int index) {
|
|
// CPPONLY: assert index < length && index >= 0: "Index out of bounds";
|
|
return names[index];
|
|
}
|
|
|
|
// CPPONLY: /**
|
|
// CPPONLY: * Obtains a line number without bounds check.
|
|
// CPPONLY: * @param index a valid attribute index
|
|
// CPPONLY: * @return the line number at index or -1 if unknown
|
|
// CPPONLY: */
|
|
// CPPONLY: public int getLineNoBoundsCheck(int index) {
|
|
// CPPONLY: assert index < length && index >= 0: "Index out of bounds";
|
|
// CPPONLY: return lines[index];
|
|
// CPPONLY: }
|
|
|
|
// [NOCPP[
|
|
|
|
/**
|
|
* Variant of <code>getQName(int index)</code> without bounds check.
|
|
* @param index a valid attribute index
|
|
* @return the QName at index
|
|
*/
|
|
public @QName String getQNameNoBoundsCheck(int index) {
|
|
return names[index].getQName(mode);
|
|
}
|
|
|
|
/**
|
|
* Variant of <code>getType(int index)</code> without bounds check.
|
|
* @param index a valid attribute index
|
|
* @return the attribute type at index
|
|
*/
|
|
public @IdType String getTypeNoBoundsCheck(int index) {
|
|
return (names[index] == AttributeName.ID) ? "ID" : "CDATA";
|
|
}
|
|
|
|
public int getIndex(String qName) {
|
|
for (int i = 0; i < length; i++) {
|
|
if (names[i].getQName(mode).equals(qName)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public int getIndex(String uri, String localName) {
|
|
for (int i = 0; i < length; i++) {
|
|
if (names[i].getLocal(mode).equals(localName)
|
|
&& names[i].getUri(mode).equals(uri)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public @IdType String getType(String qName) {
|
|
int index = getIndex(qName);
|
|
if (index == -1) {
|
|
return null;
|
|
} else {
|
|
return getType(index);
|
|
}
|
|
}
|
|
|
|
public @IdType String getType(String uri, String localName) {
|
|
int index = getIndex(uri, localName);
|
|
if (index == -1) {
|
|
return null;
|
|
} else {
|
|
return getType(index);
|
|
}
|
|
}
|
|
|
|
public String getValue(String qName) {
|
|
int index = getIndex(qName);
|
|
if (index == -1) {
|
|
return null;
|
|
} else {
|
|
return getValue(index);
|
|
}
|
|
}
|
|
|
|
public String getValue(String uri, String localName) {
|
|
int index = getIndex(uri, localName);
|
|
if (index == -1) {
|
|
return null;
|
|
} else {
|
|
return getValue(index);
|
|
}
|
|
}
|
|
|
|
public @Local String getLocalName(int index) {
|
|
if (index < length && index >= 0) {
|
|
return names[index].getLocal(mode);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public @QName String getQName(int index) {
|
|
if (index < length && index >= 0) {
|
|
return names[index].getQName(mode);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public @IdType String getType(int index) {
|
|
if (index < length && index >= 0) {
|
|
return (names[index] == AttributeName.ID) ? "ID" : "CDATA";
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public AttributeName getAttributeName(int index) {
|
|
if (index < length && index >= 0) {
|
|
return names[index];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public @NsUri String getURI(int index) {
|
|
if (index < length && index >= 0) {
|
|
return names[index].getUri(mode);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public @Prefix String getPrefix(int index) {
|
|
if (index < length && index >= 0) {
|
|
return names[index].getPrefix(mode);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public String getValue(int index) {
|
|
if (index < length && index >= 0) {
|
|
return values[index];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public String getId() {
|
|
return idValue;
|
|
}
|
|
|
|
public int getXmlnsLength() {
|
|
return xmlnsLength;
|
|
}
|
|
|
|
public @Local String getXmlnsLocalName(int index) {
|
|
if (index < xmlnsLength && index >= 0) {
|
|
return xmlnsNames[index].getLocal(mode);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public @NsUri String getXmlnsURI(int index) {
|
|
if (index < xmlnsLength && index >= 0) {
|
|
return xmlnsNames[index].getUri(mode);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public String getXmlnsValue(int index) {
|
|
if (index < xmlnsLength && index >= 0) {
|
|
return xmlnsValues[index];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public int getXmlnsIndex(AttributeName name) {
|
|
for (int i = 0; i < xmlnsLength; i++) {
|
|
if (xmlnsNames[i] == name) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public String getXmlnsValue(AttributeName name) {
|
|
int index = getXmlnsIndex(name);
|
|
if (index == -1) {
|
|
return null;
|
|
} else {
|
|
return getXmlnsValue(index);
|
|
}
|
|
}
|
|
|
|
public AttributeName getXmlnsAttributeName(int index) {
|
|
if (index < xmlnsLength && index >= 0) {
|
|
return xmlnsNames[index];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ]NOCPP]
|
|
|
|
void addAttribute(AttributeName name, String value
|
|
// [NOCPP[
|
|
, XmlViolationPolicy xmlnsPolicy
|
|
// ]NOCPP]
|
|
// CPPONLY: , int line
|
|
) throws SAXException {
|
|
// [NOCPP[
|
|
if (name == AttributeName.ID) {
|
|
idValue = value;
|
|
}
|
|
|
|
if (name.isXmlns()) {
|
|
if (xmlnsNames.length == xmlnsLength) {
|
|
int newLen = xmlnsLength == 0 ? 2 : xmlnsLength << 1;
|
|
AttributeName[] newNames = new AttributeName[newLen];
|
|
System.arraycopy(xmlnsNames, 0, newNames, 0, xmlnsNames.length);
|
|
xmlnsNames = newNames;
|
|
String[] newValues = new String[newLen];
|
|
System.arraycopy(xmlnsValues, 0, newValues, 0, xmlnsValues.length);
|
|
xmlnsValues = newValues;
|
|
}
|
|
xmlnsNames[xmlnsLength] = name;
|
|
xmlnsValues[xmlnsLength] = value;
|
|
xmlnsLength++;
|
|
switch (xmlnsPolicy) {
|
|
case FATAL:
|
|
// this is ugly
|
|
throw new SAXException("Saw an xmlns attribute.");
|
|
case ALTER_INFOSET:
|
|
return;
|
|
case ALLOW:
|
|
// fall through
|
|
}
|
|
}
|
|
|
|
// ]NOCPP]
|
|
|
|
if (names.length == length) {
|
|
int newLen = length << 1; // The first growth covers virtually
|
|
// 100% of elements according to
|
|
// Hixie
|
|
AttributeName[] newNames = new AttributeName[newLen];
|
|
System.arraycopy(names, 0, newNames, 0, names.length);
|
|
names = newNames;
|
|
String[] newValues = new String[newLen];
|
|
System.arraycopy(values, 0, newValues, 0, values.length);
|
|
values = newValues;
|
|
// CPPONLY: int[] newLines = new int[newLen];
|
|
// CPPONLY: System.arraycopy(lines, 0, newLines, 0, lines.length);
|
|
// CPPONLY: lines = newLines;
|
|
}
|
|
names[length] = name;
|
|
values[length] = value;
|
|
// CPPONLY: lines[length] = line;
|
|
length++;
|
|
}
|
|
|
|
void clear(int m) {
|
|
for (int i = 0; i < length; i++) {
|
|
names[i].release();
|
|
names[i] = null;
|
|
Portability.releaseString(values[i]);
|
|
values[i] = null;
|
|
}
|
|
length = 0;
|
|
mode = m;
|
|
// [NOCPP[
|
|
idValue = null;
|
|
for (int i = 0; i < xmlnsLength; i++) {
|
|
xmlnsNames[i] = null;
|
|
xmlnsValues[i] = null;
|
|
}
|
|
xmlnsLength = 0;
|
|
// ]NOCPP]
|
|
}
|
|
|
|
/**
|
|
* This is used in C++ to release special <code>isindex</code>
|
|
* attribute values whose ownership is not transferred.
|
|
*/
|
|
void releaseValue(int i) {
|
|
Portability.releaseString(values[i]);
|
|
}
|
|
|
|
/**
|
|
* This is only used for <code>AttributeName</code> ownership transfer
|
|
* in the isindex case to avoid freeing custom names twice in C++.
|
|
*/
|
|
void clearWithoutReleasingContents() {
|
|
for (int i = 0; i < length; i++) {
|
|
names[i] = null;
|
|
values[i] = null;
|
|
}
|
|
length = 0;
|
|
}
|
|
|
|
boolean contains(AttributeName name) {
|
|
for (int i = 0; i < length; i++) {
|
|
if (name.equalsAnother(names[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
// [NOCPP[
|
|
for (int i = 0; i < xmlnsLength; i++) {
|
|
if (name.equalsAnother(xmlnsNames[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
// ]NOCPP]
|
|
return false;
|
|
}
|
|
|
|
public void adjustForMath() {
|
|
mode = AttributeName.MATHML;
|
|
}
|
|
|
|
public void adjustForSvg() {
|
|
mode = AttributeName.SVG;
|
|
}
|
|
|
|
public HtmlAttributes cloneAttributes(Interner interner)
|
|
throws SAXException {
|
|
assert (length == 0
|
|
// [NOCPP[
|
|
&& xmlnsLength == 0
|
|
// ]NOCPP]
|
|
)
|
|
|| mode == 0 || mode == 3;
|
|
HtmlAttributes clone = new HtmlAttributes(0);
|
|
for (int i = 0; i < length; i++) {
|
|
clone.addAttribute(names[i].cloneAttributeName(interner),
|
|
Portability.newStringFromString(values[i])
|
|
// [NOCPP[
|
|
, XmlViolationPolicy.ALLOW
|
|
// ]NOCPP]
|
|
// CPPONLY: , lines[i]
|
|
);
|
|
}
|
|
// [NOCPP[
|
|
for (int i = 0; i < xmlnsLength; i++) {
|
|
clone.addAttribute(xmlnsNames[i], xmlnsValues[i],
|
|
XmlViolationPolicy.ALLOW);
|
|
}
|
|
// ]NOCPP]
|
|
return clone; // XXX!!!
|
|
}
|
|
|
|
public boolean equalsAnother(HtmlAttributes other) {
|
|
assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content.";
|
|
int otherLength = other.getLength();
|
|
if (length != otherLength) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < length; i++) {
|
|
// Work around the limitations of C++
|
|
boolean found = false;
|
|
// The comparing just the local names is OK, since these attribute
|
|
// holders are both supposed to belong to HTML formatting elements
|
|
@Local String ownLocal = names[i].getLocal(AttributeName.HTML);
|
|
for (int j = 0; j < otherLength; j++) {
|
|
if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) {
|
|
found = true;
|
|
if (!Portability.stringEqualsString(values[i], other.values[j])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// [NOCPP[
|
|
|
|
void processNonNcNames(TreeBuilder<?> treeBuilder, XmlViolationPolicy namePolicy) throws SAXException {
|
|
for (int i = 0; i < length; i++) {
|
|
AttributeName attName = names[i];
|
|
if (!attName.isNcName(mode)) {
|
|
String name = attName.getLocal(mode);
|
|
switch (namePolicy) {
|
|
case ALTER_INFOSET:
|
|
names[i] = AttributeName.create(NCName.escapeName(name));
|
|
// fall through
|
|
case ALLOW:
|
|
if (attName != AttributeName.XML_LANG) {
|
|
treeBuilder.warn("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
|
|
}
|
|
break;
|
|
case FATAL:
|
|
treeBuilder.fatal("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void merge(HtmlAttributes attributes) throws SAXException {
|
|
int len = attributes.getLength();
|
|
for (int i = 0; i < len; i++) {
|
|
AttributeName name = attributes.getAttributeNameNoBoundsCheck(i);
|
|
if (!contains(name)) {
|
|
addAttribute(name, attributes.getValueNoBoundsCheck(i), XmlViolationPolicy.ALLOW);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ]NOCPP]
|
|
|
|
}
|