parent.setNoValueOptionFound();
} else if (atts.getIndex("", "value") > -1
&& "".equals(atts.getValue("", "value"))) {
parent.setEmptyValueOptionFound();
} else {
parent.setNonEmptyOption((new LocatorImpl(
getDocumentLocator())));
}
}
// Obsolete elements
if (OBSOLETE_ELEMENTS.get(localName) != null) {
err("The \u201C" + localName + "\u201D element is obsolete. "
+ OBSOLETE_ELEMENTS.get(localName));
}
// Exclusions
Integer maskAsObject;
int mask = 0;
String descendantUiString = "";
if ((maskAsObject = ANCESTOR_MASK_BY_DESCENDANT.get(localName)) != null) {
mask = maskAsObject.intValue();
descendantUiString = localName;
} else if ("video" == localName && controls) {
mask = A_BUTTON_MASK;
descendantUiString = "video\u201D with the attribute \u201Ccontrols";
} else if ("audio" == localName && controls) {
mask = A_BUTTON_MASK;
descendantUiString = "audio\u201D with the attribute \u201Ccontrols";
} else if ("menu" == localName && toolbar) {
mask = A_BUTTON_MASK;
descendantUiString = "menu\u201D with the attribute \u201Ctype=toolbar";
} else if ("img" == localName && usemap) {
mask = A_BUTTON_MASK;
descendantUiString = "img\u201D with the attribute \u201Cusemap";
} else if ("object" == localName && usemap) {
mask = A_BUTTON_MASK;
descendantUiString = "object\u201D with the attribute \u201Cusemap";
} else if ("input" == localName && !hidden) {
mask = A_BUTTON_MASK;
descendantUiString = "input";
}
if (mask != 0) {
int maskHit = ancestorMask & mask;
if (maskHit != 0) {
for (int j = 0; j < SPECIAL_ANCESTORS.length; j++) {
if ((maskHit & 1) != 0) {
err("The element \u201C"
+ descendantUiString
+ "\u201D must not appear as a descendant of the \u201C"
+ SPECIAL_ANCESTORS[j] + "\u201D element.");
}
maskHit >>= 1;
}
}
}
// Ancestor requirements/restrictions
if ("area" == localName && ((ancestorMask & MAP_MASK) == 0)) {
err("The \u201Carea\u201D element must have a \u201Cmap\u201D ancestor.");
} else if ("img" == localName) {
String titleVal = atts.getValue("", "title");
if (ismap && ((ancestorMask & HREF_MASK) == 0)) {
err("The \u201Cimg\u201D element with the "
+ "\u201Cismap\u201D attribute set must have an "
+ "\u201Ca\u201D ancestor with the "
+ "\u201Chref\u201D attribute.");
}
if (atts.getIndex("", "alt") < 0) {
if (w3cBranding || (titleVal == null || "".equals(titleVal))) {
if ((ancestorMask & FIGURE_MASK) == 0) {
err("An \u201Cimg\u201D element must have an"
+ " \u201Calt\u201D attribute, except under"
+ " certain conditions. For details, consult"
+ " guidance on providing text alternatives"
+ " for images.");
} else {
stack[currentFigurePtr].setFigcaptionNeeded();
stack[currentFigurePtr].addImageLackingAlt(new LocatorImpl(
getDocumentLocator()));
}
}
}
} else if ("input" == localName || "button" == localName
|| "select" == localName || "textarea" == localName
|| "keygen" == localName) {
for (Map.Entry<StackNode, Locator> entry : openLabels.entrySet()) {
StackNode node = entry.getKey();
Locator locator = entry.getValue();
if (node.isLabeledDescendants()) {
err("The \u201Clabel\u201D element may contain at most one \u201Cinput\u201D, \u201Cbutton\u201D, \u201Cselect\u201D, \u201Ctextarea\u201D, or \u201Ckeygen\u201D descendant.");
warn(
"\u201Clabel\u201D element with multiple labelable descendants.",
locator);
} else {
node.setLabeledDescendants();
}
}
if ((ancestorMask & LABEL_FOR_MASK) != 0) {
boolean hasMatchingFor = false;
for (int i = 0; (stack[currentPtr - i].getAncestorMask() & LABEL_FOR_MASK) != 0; i++) {
String forVal = stack[currentPtr - i].getForAttr();
if (forVal != null && forVal.equals(id)) {
hasMatchingFor = true;
break;
}
}
if (id == null || !hasMatchingFor) {
err("Any \u201C"
+ localName
+ "\u201D descendant of a \u201Clabel\u201D element with a \u201Cfor\u201D attribute must have an ID value that matches that \u201Cfor\u201D attribute.");
}
}
} else if ("table" == localName) {
if (atts.getIndex("", "summary") >= 0) {
errObsoleteAttribute("summary", "table",
" Consider describing the structure of the"
+ " \u201Ctable\u201D in a \u201Ccaption\u201D "
+ " element or in a \u201Cfigure\u201D element "
+ " containing the \u201Ctable\u201D; or,"
+ " simplify the structure of the"
+ " \u201Ctable\u201D so that no description"
+ " is needed.");
}
if (atts.getIndex("", "border") > -1) {
if (w3cBranding) {
if (atts.getIndex("", "border") > -1
&& (!("".equals(atts.getValue("", "border")) || "1".equals(atts.getValue(
"", "border"))))) {
errObsoleteAttribute("border", "table",
" Use CSS instead.");
} else {
warnPresentationalAttribute("border", "table",
" For example: \u201Ctable, td, th { border: 1px solid gray }\u201D");
}
} else {
errObsoleteAttribute("border", "table",
" Use CSS instead.");
}
}
} else if ("track" == localName && atts.getIndex("", "default") >= 0) {
for (Map.Entry<StackNode, TaintableLocatorImpl> entry : openMediaElements.entrySet()) {
StackNode node = entry.getKey();
TaintableLocatorImpl locator = entry.getValue();
if (node.isTrackDescendant()) {
err("The \u201Cdefault\u201D attribute must not occur"
+ " on more than one \u201Ctrack\u201D element"
+ " within the same \u201Caudio\u201D or"
+ " \u201Cvideo\u201D element.");
if (!locator.isTainted()) {
warn("\u201Caudio\u201D or \u201Cvideo\u201D element"
+ " has more than one \u201Ctrack\u201D child"
+ " element with a \u201Cdefault\u201D attribute.",
locator);
locator.markTainted();
}
} else {
node.setTrackDescendants();
}
}
} else if ("main" == localName) {
if (hasMain) {
err("A document must not include more than one"
+ " \u201Cmain\u201D element.");
}
hasMain = true;
}
// progress
else if ("progress" == localName) {
double value = getDoubleAttribute(atts, "value");
if (!Double.isNaN(value)) {
double max = getDoubleAttribute(atts, "max");
if (Double.isNaN(max)) {
if (!(value <= 1.0)) {
err("The value of the \u201Cvalue\u201D attribute must be less than or equal to one when the \u201Cmax\u201D attribute is absent.");
}
} else {
if (!(value <= max)) {
err("The value of the \u201Cvalue\u201D attribute must be less than or equal to the value of the \u201Cmax\u201D attribute.");
}
}
}
}
// meter
else if ("meter" == localName) {
double value = getDoubleAttribute(atts, "value");
double min = getDoubleAttribute(atts, "min");
double max = getDoubleAttribute(atts, "max");
double optimum = getDoubleAttribute(atts, "optimum");
double low = getDoubleAttribute(atts, "low");
double high = getDoubleAttribute(atts, "high");
if (!Double.isNaN(min) && !Double.isNaN(value)
&& !(min <= value)) {
err("The value of the \u201Cmin\u201D attribute must be less than or equal to the value of the \u201Cvalue\u201D attribute.");
}
if (Double.isNaN(min) && !Double.isNaN(value) && !(0 <= value)) {
err("The value of the \u201Cvalue\u201D attribute must be greater than or equal to zero when the \u201Cmin\u201D attribute is absent.");
}
if (!Double.isNaN(value) && !Double.isNaN(max)
&& !(value <= max)) {
err("The value of the \u201Cvalue\u201D attribute must be less than or equal to the value of the \u201Cmax\u201D attribute.");
}
if (!Double.isNaN(value) && Double.isNaN(max) && !(value <= 1)) {
err("The value of the \u201Cvalue\u201D attribute must be less than or equal to one when the \u201Cmax\u201D attribute is absent.");
}
if (!Double.isNaN(min) && !Double.isNaN(max) && !(min <= max)) {
err("The value of the \u201Cmin\u201D attribute must be less than or equal to the value of the \u201Cmax\u201D attribute.");
}
if (Double.isNaN(min) && !Double.isNaN(max) && !(0 <= max)) {
err("The value of the \u201Cmax\u201D attribute must be greater than or equal to zero when the \u201Cmin\u201D attribute is absent.");
}
if (!Double.isNaN(min) && Double.isNaN(max) && !(min <= 1)) {
err("The value of the \u201Cmin\u201D attribute must be less than or equal to one when the \u201Cmax\u201D attribute is absent.");
}
if (!Double.isNaN(min) && !Double.isNaN(low) && !(min <= low)) {
err("The value of the \u201Cmin\u201D attribute must be less than or equal to the value of the \u201Clow\u201D attribute.");
}
if (Double.isNaN(min) && !Double.isNaN(low) && !(0 <= low)) {
err("The value of the \u201Clow\u201D attribute must be greater than or equal to zero when the \u201Cmin\u201D attribute is absent.");
}
if (!Double.isNaN(min) && !Double.isNaN(high) && !(min <= high)) {
err("The value of the \u201Cmin\u201D attribute must be less than or equal to the value of the \u201Chigh\u201D attribute.");
}
if (Double.isNaN(min) && !Double.isNaN(high) && !(0 <= high)) {
err("The value of the \u201Chigh\u201D attribute must be greater than or equal to zero when the \u201Cmin\u201D attribute is absent.");
}
if (!Double.isNaN(low) && !Double.isNaN(high) && !(low <= high)) {
err("The value of the \u201Clow\u201D attribute must be less than or equal to the value of the \u201Chigh\u201D attribute.");
}
if (!Double.isNaN(high) && !Double.isNaN(max) && !(high <= max)) {
err("The value of the \u201Chigh\u201D attribute must be less than or equal to the value of the \u201Cmax\u201D attribute.");
}
if (!Double.isNaN(high) && Double.isNaN(max) && !(high <= 1)) {
err("The value of the \u201Chigh\u201D attribute must be less than or equal to one when the \u201Cmax\u201D attribute is absent.");
}
if (!Double.isNaN(low) && !Double.isNaN(max) && !(low <= max)) {
err("The value of the \u201Clow\u201D attribute must be less than or equal to the value of the \u201Cmax\u201D attribute.");
}
if (!Double.isNaN(low) && Double.isNaN(max) && !(low <= 1)) {
err("The value of the \u201Clow\u201D attribute must be less than or equal to one when the \u201Cmax\u201D attribute is absent.");
}
if (!Double.isNaN(min) && !Double.isNaN(optimum)
&& !(min <= optimum)) {
err("The value of the \u201Cmin\u201D attribute must be less than or equal to the value of the \u201Coptimum\u201D attribute.");
}
if (Double.isNaN(min) && !Double.isNaN(optimum)
&& !(0 <= optimum)) {
err("The value of the \u201Coptimum\u201D attribute must be greater than or equal to zero when the \u201Cmin\u201D attribute is absent.");
}
if (!Double.isNaN(optimum) && !Double.isNaN(max)
&& !(optimum <= max)) {
err("The value of the \u201Coptimum\u201D attribute must be less than or equal to the value of the \u201Cmax\u201D attribute.");
}
if (!Double.isNaN(optimum) && Double.isNaN(max)
&& !(optimum <= 1)) {
err("The value of the \u201Coptimum\u201D attribute must be less than or equal to one when the \u201Cmax\u201D attribute is absent.");
}
}
// map required attrs
else if ("map" == localName && id != null) {
String nameVal = atts.getValue("", "name");
if (nameVal != null && !nameVal.equals(id)) {
err("The \u201Cid\u201D attribute on a \u201Cmap\u201D element must have an the same value as the \u201Cname\u201D attribute.");
}
}
// script
else if ("script" == localName) {
// script language
if (languageJavaScript && typeNotTextJavaScript) {
err("A \u201Cscript\u201D element with the \u201Clanguage=\"JavaScript\"\u201D attribute set must not have a \u201Ctype\u201D attribute whose value is not \u201Ctext/javascript\u201D.");
}
// src-less script
if (atts.getIndex("", "src") < 0) {
if (atts.getIndex("", "charset") >= 0) {
err("Element \u201Cscript\u201D must not have attribute \u201Ccharset\u201D unless attribute \u201Csrc\u201D is also specified.");
}
if (atts.getIndex("", "defer") >= 0) {
err("Element \u201Cscript\u201D must not have attribute \u201Cdefer\u201D unless attribute \u201Csrc\u201D is also specified.");
}
if (atts.getIndex("", "async") >= 0) {
err("Element \u201Cscript\u201D must not have attribute \u201Casync\u201D unless attribute \u201Csrc\u201D is also specified.");
}
}
}
// bdo required attrs
else if ("bdo" == localName && atts.getIndex("", "dir") < 0) {
err("Element \u201Cbdo\u201D must have attribute \u201Cdir\u201D.");
}
// lang and xml:lang for XHTML5
if (lang != null && xmlLang != null
&& !equalsIgnoreAsciiCase(lang, xmlLang)) {
err("When the attribute \u201Clang\u201D in no namespace and the attribute \u201Clang\u201D in the XML namespace are both present, they must have the same value.");
}
// contextmenu
if (contextmenu != null) {
contextmenuReferences.add(new IdrefLocator(new LocatorImpl(
getDocumentLocator()), contextmenu));
}
if ("menu" == localName) {
menuIds.addAll(ids);
}
if (role != null && owns != null) {
for (Set<String> value : REQUIRED_ROLE_ANCESTOR_BY_DESCENDANT.values()) {
if (value.contains(role)) {
String[] ownedIds = AttributeUtil.split(owns);
for (int i = 0; i < ownedIds.length; i++) {
Set<String> ownedIdsForThisRole = ariaOwnsIdsByRole.get(role);
if (ownedIdsForThisRole == null) {
ownedIdsForThisRole = new HashSet<String>();
}
ownedIdsForThisRole.add(ownedIds[i]);
ariaOwnsIdsByRole.put(role, ownedIdsForThisRole);
}
break;
}
}
}
if ("datalist" == localName) {
listIds.addAll(ids);
}
// label for
if ("label" == localName) {
String forVal = atts.getValue("", "for");
if (forVal != null) {
formControlReferences.add(new IdrefLocator(new LocatorImpl(
getDocumentLocator()), forVal));
}
}
if (("input" == localName && !hidden) || "textarea" == localName
|| "select" == localName || "button" == localName
|| "keygen" == localName || "output" == localName) {
formControlIds.addAll(ids);
}
// input list
if ("input" == localName && list != null) {
listReferences.add(new IdrefLocator(new LocatorImpl(
getDocumentLocator()), list));
}
// input@type=button
if ("input" == localName
&& lowerCaseLiteralEqualsIgnoreAsciiCaseString("button",
atts.getValue("", "type"))) {
if (atts.getValue("", "value") == null
|| "".equals(atts.getValue("", "value"))) {
err("Element \u201Cinput\u201D with attribute \u201Ctype\u201D whose value is \u201Cbutton\u201D must have non-empty attribute \u201Cvalue\u201D.");
}
}
// track
if ("track" == localName) {
if ("".equals(atts.getValue("", "label"))) {
err("Attribute \u201Clabel\u201D for element \u201Ctrack\u201D must have non-empty value.");
}
}
// multiple selected options
if ("option" == localName && selected) {
for (Map.Entry<StackNode, Locator> entry : openSingleSelects.entrySet()) {
StackNode node = entry.getKey();
if (node.isSelectedOptions()) {
err("The \u201Cselect\u201D element cannot have more than one selected \u201Coption\u201D descendant unless the \u201Cmultiple\u201D attribute is specified.");
} else {
node.setSelectedOptions();
}
}
}
if ("meta" == localName) {
if (lowerCaseLiteralEqualsIgnoreAsciiCaseString(
"content-language", atts.getValue("", "http-equiv"))) {
err("Using the \u201Cmeta\u201D element to specify the"
+ " document-wide default language is obsolete."
+ " Consider specifying the language on the root"
+ " element instead.");
}
}
// microdata
if (itemid && !(itemscope && itemtype)) {
err("The \u201Citemid\u201D attribute must not be specified on elements that do not have both an \u201Citemscope\u201D attribute and an \u201Citemtype\u201D attribute specified.");
}
if (itemref && !itemscope) {
err("The \u201Citemref\u201D attribute must not be specified on elements that do not have an \u201Citemscope\u201D attribute specified.");
}
if (itemtype && !itemscope) {
err("The \u201Citemtype\u201D attribute must not be specified on elements that do not have an \u201Citemscope\u201D attribute specified.");
}
} else {
int len = atts.getLength();
for (int i = 0; i < len; i++) {
if (atts.getType(i) == "ID") {
String attVal = atts.getValue(i);
if (attVal.length() != 0) {
ids.add(attVal);
}
}
String attLocal = atts.getLocalName(i);
if (atts.getURI(i).length() == 0) {
if ("role" == attLocal) {
role = atts.getValue(i);
} else if ("aria-activedescendant" == attLocal) {
activeDescendant = atts.getValue(i);
} else if ("aria-owns" == attLocal) {
owns = atts.getValue(i);
}
}
}
allIds.addAll(ids);
}
// ARIA required owner/ancestors
Set<String> requiredAncestorRoles = REQUIRED_ROLE_ANCESTOR_BY_DESCENDANT.get(role);
if (requiredAncestorRoles != null && !"presentation".equals(parentRole)
&& !"tbody".equals(localName) && !"tfoot".equals(localName)
&& !"thead".equals(localName)) {
if (!currentElementHasRequiredAncestorRole(requiredAncestorRoles)) {
if (atts.getIndex("", "id") > -1
&& !"".equals(atts.getValue("", "id"))) {
needsAriaOwner.add(new IdrefLocator(new LocatorImpl(
getDocumentLocator()), atts.getValue("", "id"),
role));
} else {
errContainedInOrOwnedBy(role, getDocumentLocator());
}
}
}
// ARIA IDREFS
for (String att : MUST_NOT_DANGLE_IDREFS) {
String attVal = atts.getValue("", att);
if (attVal != null) {
String[] tokens = AttributeUtil.split(attVal);
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i];
ariaReferences.add(new IdrefLocator(getDocumentLocator(),
token, att));
}
}
}
allIds.addAll(ids);
// aria-activedescendant accompanied by aria-owns
if (activeDescendant != null && !"".equals(activeDescendant)) {
// String activeDescendantVal = atts.getValue("",
// "aria-activedescendant");
if (owns != null && !"".equals(owns)) {
activeDescendantWithAriaOwns = true;
// String[] tokens = AttributeUtil.split(owns);
// for (int i = 0; i < tokens.length; i++) {
// String token = tokens[i];
// if (token.equals(activeDescendantVal)) {
// activeDescendantWithAriaOwns = true;
// break;
// }
// }
}
}
// activedescendant
for (Iterator<Map.Entry<StackNode, Locator>> iterator = openActiveDescendants.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<StackNode, Locator> entry = iterator.next();
if (ids.contains(entry.getKey().getActiveDescendant())) {
iterator.remove();
}
}
if ("http://www.w3.org/1999/xhtml" == uri) {
int number = specialAncestorNumber(localName);
if (number > -1) {
ancestorMask |= (1 << number);
}
if ("a" == localName && href) {
ancestorMask |= HREF_MASK;
}
StackNode child = new StackNode(ancestorMask, localName, role,
activeDescendant, forAttr);
if (activeDescendant != null && !activeDescendantWithAriaOwns) {
openActiveDescendants.put(child, new LocatorImpl(
getDocumentLocator()));
}
if ("select" == localName && atts.getIndex("", "multiple") == -1) {
openSingleSelects.put(child, getDocumentLocator());
} else if ("label" == localName) {
openLabels.put(child, new LocatorImpl(getDocumentLocator()));
} else if ("video" == localName || "audio" == localName ) {
openMediaElements.put(child, new TaintableLocatorImpl(getDocumentLocator()));
}
push(child);
if ("select" == localName && atts.getIndex("", "required") > -1
&& atts.getIndex("", "multiple") < 0) {
if (atts.getIndex("", "size") > -1) {
String size = trimSpaces(atts.getValue("", "size"));
if (!"".equals(size)) {
try {
if ((size.length() > 1 && size.charAt(0) == '+' && Integer.parseInt(size.substring(1)) == 1)
|| Integer.parseInt(size) == 1) {
child.setOptionNeeded();
} else {
// do nothing
}
} catch (NumberFormatException e) {
}
}
} else {
// default size is 1
child.setOptionNeeded();
}
}
} else {
StackNode child = new StackNode(ancestorMask, null, role,
activeDescendant, forAttr);
if (activeDescendant != null) {
openActiveDescendants.put(child, new LocatorImpl(
getDocumentLocator()));
}
push(child);
}