Search in sources :

Example 1 with LocatorImpl

use of nu.validator.checker.LocatorImpl in project validator by validator.

the class Assertions method startElement.

/**
 * @see nu.validator.checker.Checker#startElement(java.lang.String,
 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
 */
@Override
public void startElement(String uri, String localName, String name, Attributes atts) throws SAXException {
    if ("http://www.w3.org/1999/xhtml" == uri && "template".equals(localName)) {
        numberOfTemplatesDeep++;
        if (numberOfTemplatesDeep != 1) {
            return;
        }
    } else if (numberOfTemplatesDeep > 0) {
        return;
    }
    Set<String> ids = new HashSet<>();
    String role = null;
    String inputTypeVal = null;
    String activeDescendant = null;
    String owns = null;
    String forAttr = null;
    boolean href = false;
    boolean activeDescendantWithAriaOwns = false;
    // see nu.validator.datatype.ImageCandidateStrings
    System.setProperty("nu.validator.checker.imageCandidateString.hasWidth", "0");
    StackNode parent = peek();
    int ancestorMask = 0;
    String parentRole = null;
    String parentName = null;
    if (parent != null) {
        ancestorMask = parent.getAncestorMask();
        parentName = parent.getName();
        parentRole = parent.getRole();
    }
    if ("http://www.w3.org/1999/xhtml" == uri) {
        boolean controls = false;
        boolean hidden = false;
        boolean toolbar = false;
        boolean usemap = false;
        boolean ismap = false;
        boolean selected = false;
        boolean itemid = false;
        boolean itemref = false;
        boolean itemscope = false;
        boolean itemtype = false;
        boolean tabindex = false;
        boolean languageJavaScript = false;
        boolean typeNotTextJavaScript = false;
        String xmlLang = null;
        String lang = null;
        String id = null;
        String list = null;
        int len = atts.getLength();
        for (int i = 0; i < len; i++) {
            String attUri = atts.getURI(i);
            if (attUri.length() == 0) {
                String attLocal = atts.getLocalName(i);
                if ("embed".equals(localName)) {
                    for (int j = 0; j < attLocal.length(); j++) {
                        char c = attLocal.charAt(j);
                        if (c >= 'A' && c <= 'Z') {
                            err("Bad attribute name \u201c" + attLocal + "\u201d. Attribute names for the" + " \u201cembed\u201d element must not" + " contain uppercase ASCII letters.");
                        }
                    }
                    if (!NCName.isNCName(attLocal)) {
                        err("Bad attribute name \u201c" + attLocal + "\u201d. Attribute names for the" + " \u201cembed\u201d element must be" + " XML-compatible.");
                    }
                }
                if ("style" == attLocal) {
                    String styleContents = atts.getValue(i);
                    ApplContext ac = new ApplContext("en");
                    ac.setCssVersionAndProfile("css3svg");
                    ac.setMedium("all");
                    ac.setTreatVendorExtensionsAsWarnings(true);
                    ac.setTreatCssHacksAsWarnings(true);
                    ac.setWarningLevel(-1);
                    ac.setFakeURL("file://localhost/StyleAttribute");
                    // 
                    StyleSheetParser styleSheetParser = new StyleSheetParser();
                    styleSheetParser.parseStyleAttribute(ac, new ByteArrayInputStream(styleContents.getBytes()), "", ac.getFakeURL(), getDocumentLocator().getLineNumber());
                    styleSheetParser.getStyleSheet().findConflicts(ac);
                    // 
                    Errors errors = styleSheetParser.getStyleSheet().getErrors();
                    if (errors.getErrorCount() > 0) {
                        incrementUseCounter("style-attribute-errors-found");
                    }
                    for (int j = 0; j < errors.getErrorCount(); j++) {
                        String message = "";
                        String cssProperty = "";
                        String cssMessage = "";
                        CssError error = errors.getErrorAt(j);
                        Throwable ex = error.getException();
                        if (ex instanceof CssParseException) {
                            CssParseException cpe = (CssParseException) ex;
                            if (// 
                            "generator.unrecognize".equals(cpe.getErrorType())) {
                                cssMessage = "Parse Error";
                            }
                            if (cpe.getProperty() != null) {
                                cssProperty = String.format("\u201c%s\u201D: ", cpe.getProperty());
                            }
                            if (cpe.getMessage() != null) {
                                cssMessage = cpe.getMessage();
                            }
                            if (!"".equals(cssMessage)) {
                                message = cssProperty + cssMessage + ".";
                            }
                        } else {
                            message = ex.getMessage();
                        }
                        if (!"".equals(message)) {
                            err("CSS: " + message);
                        }
                    }
                } else if ("tabindex" == attLocal) {
                    tabindex = true;
                } else if ("href" == attLocal) {
                    href = true;
                } else if ("controls" == attLocal) {
                    controls = true;
                } else if ("type" == attLocal && "param" != localName && "ol" != localName && "ul" != localName && "li" != localName) {
                    if ("input" == localName) {
                        inputTypeVal = atts.getValue(i).toLowerCase();
                    }
                    String attValue = atts.getValue(i);
                    if (AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("hidden", attValue)) {
                        hidden = true;
                    } else if (AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("toolbar", attValue)) {
                        toolbar = true;
                    }
                    if (!AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("text/javascript", attValue)) {
                        typeNotTextJavaScript = true;
                    }
                } else 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);
                } else if ("list" == attLocal) {
                    list = atts.getValue(i);
                } else if ("lang" == attLocal) {
                    lang = atts.getValue(i);
                } else if ("id" == attLocal) {
                    id = atts.getValue(i);
                } else if ("for" == attLocal && "label" == localName) {
                    forAttr = atts.getValue(i);
                    ancestorMask |= LABEL_FOR_MASK;
                } else if ("ismap" == attLocal) {
                    ismap = true;
                } else if ("selected" == attLocal) {
                    selected = true;
                } else if ("usemap" == attLocal && "input" != localName) {
                    usemap = true;
                } else if ("itemid" == attLocal) {
                    itemid = true;
                } else if ("itemref" == attLocal) {
                    itemref = true;
                } else if ("itemscope" == attLocal) {
                    itemscope = true;
                } else if ("itemtype" == attLocal) {
                    itemtype = true;
                } else if ("language" == attLocal && AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("javascript", atts.getValue(i))) {
                    languageJavaScript = true;
                } else if ("rev" == attLocal && !("1".equals(System.getProperty("nu.validator.schema.rdfa-full")))) {
                    errObsoleteAttribute("rev", localName, " Use the \u201Crel\u201D attribute instead," + " with a term having the opposite meaning.");
                } else if (OBSOLETE_ATTRIBUTES.containsKey(attLocal) && "ol" != localName && "ul" != localName && "li" != localName) {
                    String[] elementNames = OBSOLETE_ATTRIBUTES.get(attLocal);
                    Arrays.sort(elementNames);
                    if (Arrays.binarySearch(elementNames, localName) >= 0) {
                        String suggestion = OBSOLETE_ATTRIBUTES_MSG.containsKey(attLocal) ? " " + OBSOLETE_ATTRIBUTES_MSG.get(attLocal) : "";
                        errObsoleteAttribute(attLocal, localName, suggestion);
                    }
                } else if (OBSOLETE_STYLE_ATTRS.containsKey(attLocal)) {
                    String[] elementNames = OBSOLETE_STYLE_ATTRS.get(attLocal);
                    Arrays.sort(elementNames);
                    if (Arrays.binarySearch(elementNames, localName) >= 0) {
                        errObsoleteAttribute(attLocal, localName, " Use CSS instead.");
                    }
                } else if (INPUT_ATTRIBUTES.containsKey(attLocal) && "input" == localName) {
                    String[] allowedTypes = INPUT_ATTRIBUTES.get(attLocal);
                    Arrays.sort(allowedTypes);
                    inputTypeVal = inputTypeVal == null ? "text" : inputTypeVal;
                    if (Arrays.binarySearch(allowedTypes, inputTypeVal) < 0) {
                        err("Attribute \u201c" + attLocal + "\u201d is only allowed when the input" + " type is " + renderTypeList(allowedTypes) + ".");
                    }
                } else if ("autofocus" == attLocal) {
                    if (hasAutofocus) {
                        err("A document must not include more than one" + " \u201Cautofocus\u201D attribute.");
                    }
                    hasAutofocus = true;
                } else if (ATTRIBUTES_WITH_IMPLICIT_STATE_OR_PROPERTY.contains(attLocal)) {
                    String stateOrProperty = "aria-" + attLocal;
                    if (atts.getIndex("", stateOrProperty) > -1 && "true".equals(atts.getValue("", stateOrProperty))) {
                        warn("Attribute \u201C" + stateOrProperty + "\u201D is unnecessary for elements that" + " have attribute \u201C" + attLocal + "\u201D.");
                    }
                }
            } else if ("http://www.w3.org/XML/1998/namespace" == attUri) {
                if ("lang" == atts.getLocalName(i)) {
                    xmlLang = atts.getValue(i);
                }
            }
            if (atts.getType(i) == "ID" || "id" == atts.getLocalName(i)) {
                String attVal = atts.getValue(i);
                if (attVal.length() != 0) {
                    ids.add(attVal);
                }
            }
        }
        if (followW3Cspec) {
            if (("input".equals(localName) || "textarea".equals(localName)) && atts.getIndex("", "inputmode") > -1) {
                err("The \u201cinputmode\u201d attribute is not allowed by" + " the W3C HTML specification.");
            }
        }
        if ("input".equals(localName)) {
            if (atts.getIndex("", "name") > -1 && "isindex".equals(atts.getValue("", "name"))) {
                err("The value \u201cisindex\u201d for the \u201cname\u201d" + " attribute of the \u201cinput\u201d element is" + " not allowed.");
            }
            inputTypeVal = inputTypeVal == null ? "text" : inputTypeVal;
            if (atts.getIndex("", "autocomplete") > -1) {
                Class<?> datatypeClass = null;
                String autocompleteVal = atts.getValue("", "autocomplete");
                try {
                    if (!"on".equals(autocompleteVal) && !"off".equals(autocompleteVal)) {
                        if ("hidden".equals(inputTypeVal)) {
                            AutocompleteDetailsAny.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsAny.class;
                        } else if ("text".equals(inputTypeVal) || "search".equals(autocompleteVal)) {
                            AutocompleteDetailsText.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsText.class;
                        } else if ("password".equals(inputTypeVal)) {
                            AutocompleteDetailsPassword.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsPassword.class;
                        } else if ("url".equals(inputTypeVal)) {
                            AutocompleteDetailsUrl.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsUrl.class;
                        } else if ("email".equals(inputTypeVal)) {
                            AutocompleteDetailsEmail.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsEmail.class;
                        } else if ("tel".equals(inputTypeVal)) {
                            AutocompleteDetailsTel.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsTel.class;
                        } else if ("number".equals(inputTypeVal)) {
                            AutocompleteDetailsNumeric.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsNumeric.class;
                        } else if ("month".equals(inputTypeVal)) {
                            AutocompleteDetailsMonth.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsMonth.class;
                        } else if ("date".equals(inputTypeVal)) {
                            AutocompleteDetailsDate.THE_INSTANCE.checkValid(autocompleteVal);
                            datatypeClass = AutocompleteDetailsDate.class;
                        }
                    }
                } catch (DatatypeException e) {
                    try {
                        if (getErrorHandler() != null) {
                            String msg = e.getMessage();
                            msg = msg.substring(msg.indexOf(": ") + 2);
                            VnuBadAttrValueException ex = new VnuBadAttrValueException(localName, uri, "autocomplete", autocompleteVal, msg, getDocumentLocator(), datatypeClass, false);
                            getErrorHandler().error(ex);
                        }
                    } catch (ClassNotFoundException ce) {
                    }
                }
            }
        }
        if ("img".equals(localName) || "source".equals(localName)) {
            if (atts.getIndex("", "srcset") > -1) {
                String srcsetVal = atts.getValue("", "srcset");
                try {
                    if (atts.getIndex("", "sizes") > -1) {
                        ImageCandidateStringsWidthRequired.THE_INSTANCE.checkValid(srcsetVal);
                    } else {
                        ImageCandidateStrings.THE_INSTANCE.checkValid(srcsetVal);
                    }
                    // see nu.validator.datatype.ImageCandidateStrings
                    if ("1".equals(System.getProperty("nu.validator.checker.imageCandidateString.hasWidth"))) {
                        if (atts.getIndex("", "sizes") < 0) {
                            err("When the \u201csrcset\u201d attribute has" + " any image candidate string with a" + " width descriptor, the" + " \u201csizes\u201d attribute" + " must also be present.");
                        }
                    }
                } catch (DatatypeException e) {
                    Class<?> datatypeClass = ImageCandidateStrings.class;
                    if (atts.getIndex("", "sizes") > -1) {
                        datatypeClass = ImageCandidateStringsWidthRequired.class;
                    }
                    try {
                        if (getErrorHandler() != null) {
                            String msg = e.getMessage();
                            if (e instanceof Html5DatatypeException) {
                                Html5DatatypeException ex5 = (Html5DatatypeException) e;
                                if (!ex5.getDatatypeClass().equals(ImageCandidateURL.class)) {
                                    msg = msg.substring(msg.indexOf(": ") + 2);
                                }
                            }
                            VnuBadAttrValueException ex = new VnuBadAttrValueException(localName, uri, "srcset", srcsetVal, msg, getDocumentLocator(), datatypeClass, false);
                            getErrorHandler().error(ex);
                        }
                    } catch (ClassNotFoundException ce) {
                    }
                }
                if ("picture".equals(parentName) && !siblingSources.isEmpty()) {
                    for (Map.Entry<Locator, Map<String, String>> entry : siblingSources.entrySet()) {
                        Locator locator = entry.getKey();
                        Map<String, String> sourceAtts = entry.getValue();
                        String media = sourceAtts.get("media");
                        if (media == null && sourceAtts.get("type") == null) {
                            err("A \u201csource\u201d element that has a" + " following sibling" + " \u201csource\u201d element or" + " \u201cimg\u201d element with a" + " \u201csrcset\u201d attribute" + " must have a" + " \u201cmedia\u201d attribute and/or" + " \u201ctype\u201d attribute.", locator);
                            siblingSources.remove(locator);
                        } else if (media != null && AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("all", trimSpaces(media))) {
                            err("Value of \u201cmedia\u201d attribute here" + " must not be \u201call\u201d.", locator);
                        }
                    }
                }
            } else if (atts.getIndex("", "sizes") > -1) {
                err("The \u201csizes\u201d attribute may be specified" + " only if the \u201csrcset\u201d attribute is" + " also present.");
            }
        }
        if ("picture".equals(parentName) && "source".equals(localName)) {
            Map<String, String> sourceAtts = new HashMap<>();
            for (int i = 0; i < atts.getLength(); i++) {
                sourceAtts.put(atts.getLocalName(i), atts.getValue(i));
            }
            siblingSources.put((new LocatorImpl(getDocumentLocator())), sourceAtts);
        }
        if ("figure" == localName) {
            currentFigurePtr = currentPtr + 1;
        }
        if ((ancestorMask & FIGURE_MASK) != 0) {
            if ("img" == localName) {
                if (stack[currentFigurePtr].hasImg()) {
                    stack[currentFigurePtr].setEmbeddedContentFound();
                } else {
                    stack[currentFigurePtr].setImgFound();
                }
            } else if ("audio" == localName || "canvas" == localName || "embed" == localName || "iframe" == localName || "math" == localName || "object" == localName || "svg" == localName || "video" == localName) {
                stack[currentFigurePtr].setEmbeddedContentFound();
            }
        }
        if ("article" == localName || "aside" == localName || "nav" == localName || "section" == localName) {
            currentSectioningElementPtr = currentPtr + 1;
            currentSectioningDepth++;
        }
        if ("h1" == localName || "h2" == localName || "h3" == localName || "h4" == localName || "h5" == localName || "h6" == localName) {
            currentHeadingPtr = currentPtr + 1;
            if (currentSectioningElementPtr > -1) {
                stack[currentSectioningElementPtr].setHeadingFound();
            }
        }
        if (((ancestorMask & H1_MASK) != 0 || (ancestorMask & H2_MASK) != 0 || (ancestorMask & H3_MASK) != 0 || (ancestorMask & H4_MASK) != 0 || (ancestorMask & H5_MASK) != 0 || (ancestorMask & H6_MASK) != 0) && "img" == localName && atts.getIndex("", "alt") > -1 && !"".equals(atts.getValue("", "alt"))) {
            stack[currentHeadingPtr].setImgFound();
        }
        if ("option" == localName && !parent.hasOption()) {
            if (atts.getIndex("", "value") < 0) {
                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 = "The element \u201C" + localName + "\u201D";
        if ((maskAsObject = ANCESTOR_MASK_BY_DESCENDANT.get(localName)) != null) {
            mask = maskAsObject.intValue();
        } else if ("video" == localName && controls) {
            mask = A_BUTTON_MASK;
            descendantUiString = "The element \u201Cvideo\u201D with the" + " attribute \u201Ccontrols\u201D";
            checkForInteractiveAncestorRole(descendantUiString);
        } else if ("audio" == localName && controls) {
            mask = A_BUTTON_MASK;
            descendantUiString = "The element \u201Caudio\u201D with the" + " attribute \u201Ccontrols\u201D";
            checkForInteractiveAncestorRole(descendantUiString);
        } else if ("menu" == localName && toolbar) {
            mask = A_BUTTON_MASK;
            descendantUiString = "The element \u201Cmenu\u201D with the" + " attribute \u201Ctype=toolbar\u201D";
            checkForInteractiveAncestorRole(descendantUiString);
        } else if ("img" == localName && usemap) {
            mask = A_BUTTON_MASK;
            descendantUiString = "The element \u201Cimg\u201D with the" + " attribute \u201Cusemap\u201D";
            checkForInteractiveAncestorRole(descendantUiString);
        } else if ("object" == localName && usemap) {
            mask = A_BUTTON_MASK;
            descendantUiString = "The element \u201Cobject\u201D with the" + " attribute \u201Cusemap\u201D";
            checkForInteractiveAncestorRole(descendantUiString);
        } else if ("input" == localName && !hidden) {
            mask = A_BUTTON_MASK;
            checkForInteractiveAncestorRole(descendantUiString);
        } else if (tabindex) {
            mask = A_BUTTON_MASK;
            descendantUiString = "An element with the attribute" + " \u201Ctabindex\u201D";
            checkForInteractiveAncestorRole(descendantUiString);
        } else if (role != null && role != "" && Arrays.binarySearch(INTERACTIVE_ROLES, role) >= 0) {
            mask = A_BUTTON_MASK;
            descendantUiString = "An element with the attribute \u201C" + "role=" + role + "\u201D";
            checkForInteractiveAncestorRole(descendantUiString);
        }
        if (mask != 0) {
            int maskHit = ancestorMask & mask;
            if (maskHit != 0) {
                for (String ancestor : SPECIAL_ANCESTORS) {
                    if ((maskHit & 1) != 0) {
                        err(descendantUiString + " must not appear as a" + " descendant of the \u201C" + ancestor + "\u201D element.");
                    }
                    maskHit >>= 1;
                }
            }
        }
        if (Arrays.binarySearch(INTERACTIVE_ELEMENTS, localName) >= 0) {
            checkForInteractiveAncestorRole("The element \u201C" + localName + "\u201D");
        }
        // 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 (followW3Cspec || (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 ("".equals(atts.getValue("", "alt")) && role != null) {
                    List<String> roles = Arrays.asList(// 
                    role.trim().toLowerCase().split("\\s+"));
                    if (!roles.contains("none") && !roles.contains("presentation")) {
                        err("An \u201Cimg\u201D element which has an" + " \u201Calt\u201D attribute whose value" + " is the empty string must not have a" + " \u201Crole\u201D attribute with any" + " value other than \u201Cnone\u201D or" + " \u201Cpresentation\u201D");
                    }
                }
            }
        } 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 (followW3Cspec) {
                    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) {
            for (int i = 0; i < currentPtr; i++) {
                String ancestorName = stack[currentPtr - i].getName();
                if (Arrays.binarySearch(PROHIBITED_MAIN_ANCESTORS, ancestorName) >= 0) {
                    err("The \u201Cmain\u201D element must not appear as a" + " descendant of the \u201C" + ancestorName + "\u201D element.");
                }
            }
            if (atts.getIndex("", "hidden") < 0) {
                if (hasVisibleMain) {
                    err("A document must not include more than one visible" + " \u201Cmain\u201D element.");
                }
                hasVisibleMain = true;
            }
        } else if ("h1" == localName) {
            if (currentSectioningDepth > 1) {
                warn(h1WarningMessage);
            } else if (currentSectioningDepth == 1) {
                secondLevelH1s.add(new LocatorImpl(getDocumentLocator()));
            } else {
                hasTopLevelH1 = true;
            }
        } else // progress
        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.");
                    }
                }
            }
        } else // meter
        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.");
            }
        } else // map required attrs
        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.");
            }
        } else if ("object" == localName) {
            if (atts.getIndex("", "typemustmatch") >= 0) {
                if ((atts.getIndex("", "data") < 0) || (atts.getIndex("", "type") < 0)) {
                    {
                        err("Element \u201Cobject\u201D must not have" + " attribute \u201Ctypemustmatch\u201D unless" + " both attribute \u201Cdata\u201D" + " and attribute \u201Ctype\u201D are also specified.");
                    }
                }
            }
        } else // script
        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.");
            }
            if (atts.getIndex("", "charset") >= 0) {
                warnObsoleteAttribute("charset", "script", "");
                if (!"utf-8".equals(atts.getValue("", "charset").toLowerCase())) {
                    err("The only allowed value for the \u201Ccharset\u201D" + " attribute for the \u201Cscript\u201D" + " element is \u201Cutf-8\u201D. (But the" + " attribute is not needed and should be" + " omitted altogether.)");
                }
            }
            // 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) {
                    if (!(// 
                    atts.getIndex("", "type") > -1 && "module".equals(// 
                    atts.getValue("", "type").toLowerCase()))) {
                        err("Element \u201Cscript\u201D must not have" + " attribute \u201Casync\u201D unless" + " attribute \u201Csrc\u201D is also" + " specified or unless attribute" + " \u201Ctype\u201D is specified with" + " value \u201Cmodule\u201D.");
                    }
                }
                if (atts.getIndex("", "integrity") >= 0) {
                    err("Element \u201Cscript\u201D must not have attribute" + " \u201Cintegrity\u201D unless attribute" + " \u201Csrc\u201D is also specified.");
                }
            }
            if (atts.getIndex("", "type") > -1) {
                String scriptType = atts.getValue("", "type").toLowerCase();
                if (JAVASCRIPT_MIME_TYPES.contains(scriptType) || "".equals(scriptType)) {
                    warn("The \u201Ctype\u201D attribute is unnecessary for" + " JavaScript resources.");
                }
                if ("module".equals(scriptType)) {
                    if (atts.getIndex("", "integrity") > -1) {
                        err("A \u201Cscript\u201D element with an" + " \u201Cintegrity\u201D attribute must not have a" + " \u201Ctype\u201D attribute with the value" + " \u201Cmodule\u201D.");
                    }
                    if (atts.getIndex("", "defer") > -1) {
                        err("A \u201Cscript\u201D element with a" + " \u201Cdefer\u201D attribute must not have a" + " \u201Ctype\u201D attribute with the value" + " \u201Cmodule\u201D.");
                    }
                    if (atts.getIndex("", "nomodule") > -1) {
                        err("A \u201Cscript\u201D element with a" + " \u201Cnomodule\u201D attribute must not have a" + " \u201Ctype\u201D attribute with the value" + " \u201Cmodule\u201D.");
                    }
                }
            }
        } else if ("style" == localName) {
            if (atts.getIndex("", "type") > -1) {
                String styleType = atts.getValue("", "type").toLowerCase();
                if ("text/css".equals(styleType)) {
                    warn(" The \u201Ctype\u201D attribute for the" + " \u201Cstyle\u201D element is not needed and" + " should be omitted.");
                } else {
                    err(" The only allowed value for the \u201Ctype\u201D" + " attribute for the \u201Cstyle\u201D element" + " is \u201Ctext/css\u201D (with no" + " parameters). (But the attribute is not" + " needed and should be omitted altogether.)");
                }
            }
        } else // bdo required attrs
        if ("bdo" == localName && atts.getIndex("", "dir") < 0) {
            err("Element \u201Cbdo\u201D must have attribute \u201Cdir\u201D.");
        }
        // labelable elements
        if ("button" == localName || ("input" == localName && !AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("hidden", atts.getValue("", "type"))) || "meter" == localName || "output" == localName || "progress" == localName || "select" == localName || "textarea" == 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 \u201Cbutton\u201D, \u201Cinput\u201D," + " \u201Cmeter\u201D, \u201Coutput\u201D," + " \u201Cprogress\u201D, \u201Cselect\u201D," + " or \u201Ctextarea\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.");
                }
            }
        }
        // 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.");
        }
        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 (String ownedId : ownedIds) {
                        Set<String> ownedIdsForThisRole = ariaOwnsIdsByRole.get(role);
                        if (ownedIdsForThisRole == null) {
                            ownedIdsForThisRole = new HashSet<>();
                        }
                        ownedIdsForThisRole.add(ownedId);
                        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 ("form" == localName) {
            formElementIds.addAll(ids);
        }
        if ((// 
        "button" == localName || // 
        "input" == localName && !hidden) || // 
        "meter" == localName || // 
        "output" == localName || // 
        "progress" == localName || // 
        "select" == localName || "textarea" == localName) {
            formControlIds.addAll(ids);
        }
        if ("button" == localName || "fieldset" == localName || ("input" == localName && !hidden) || "object" == localName || "output" == localName || "select" == localName || "textarea" == localName) {
            String formVal = atts.getValue("", "form");
            if (formVal != null) {
                formElementReferences.add(new IdrefLocator(new LocatorImpl(getDocumentLocator()), formVal));
            }
        }
        // input list
        if ("input" == localName && list != null) {
            listReferences.add(new IdrefLocator(new LocatorImpl(getDocumentLocator()), list));
        }
        // input@type=button
        if ("input" == localName && AttributeUtil.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 (AttributeUtil.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.");
            } else if (AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("x-ua-compatible", atts.getValue("", "http-equiv")) && !AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("ie=edge", atts.getValue("", "content"))) {
                err("A \u201Cmeta\u201D element with an" + " \u201Chttp-equiv\u201D attribute whose value is" + " \u201CX-UA-Compatible\u201D" + " must have a" + " \u201Ccontent\u201D attribute with the value" + " \u201CIE=edge\u201D.");
            }
            if (atts.getIndex("", "charset") > -1) {
                if (!"utf-8".equals(atts.getValue("", "charset").toLowerCase())) {
                    err("The only allowed value for the \u201Ccharset\u201D" + " attribute for the \u201Cmeta\u201D" + " element is \u201Cutf-8\u201D.");
                }
                if (hasMetaCharset) {
                    err("A document must not include more than one" + " \u201Cmeta\u201D element with a" + " \u201Ccharset\u201D attribute.");
                }
                if (hasContentTypePragma) {
                    err("A document must not include both a" + " \u201Cmeta\u201D element with an" + " \u201Chttp-equiv\u201D attribute" + " whose value is \u201Ccontent-type\u201D," + " and a \u201Cmeta\u201D element with a" + " \u201Ccharset\u201D attribute.");
                }
                hasMetaCharset = true;
            }
            if (atts.getIndex("", "name") > -1) {
                if ("description".equals(atts.getValue("", "name"))) {
                    if (hasMetaDescription) {
                        err("A document must not include more than one" + " \u201Cmeta\u201D element with its" + " \u201Cname\u201D attribute set to the" + " value \u201Cdescription\u201D.");
                    }
                    hasMetaDescription = true;
                }
                if ("viewport".equals(atts.getValue("", "name")) && atts.getIndex("", "content") > -1) {
                    String contentVal = atts.getValue("", "content").toLowerCase();
                    if (contentVal.contains("user-scalable=no") || contentVal.contains("maximum-scale=1.0")) {
                        warn("Consider avoiding viewport values that" + " prevent users from resizing documents.");
                    }
                }
                if ("theme-color".equals(atts.getValue("", "name")) && atts.getIndex("", "content") > -1) {
                    String contentVal = atts.getValue("", "content").toLowerCase();
                    try {
                        Color.THE_INSTANCE.checkValid(contentVal);
                    } catch (DatatypeException e) {
                        try {
                            if (getErrorHandler() != null) {
                                String msg = e.getMessage();
                                if (e instanceof Html5DatatypeException) {
                                    msg = msg.substring(msg.indexOf(": ") + 2);
                                }
                                // 
                                VnuBadAttrValueException ex = new VnuBadAttrValueException(localName, uri, "content", contentVal, msg, getDocumentLocator(), Color.class, false);
                                getErrorHandler().error(ex);
                            }
                        } catch (ClassNotFoundException ce) {
                        }
                    }
                }
            }
            if (atts.getIndex("", "http-equiv") > -1 && AttributeUtil.lowerCaseLiteralEqualsIgnoreAsciiCaseString("content-type", atts.getValue("", "http-equiv"))) {
                if (hasMetaCharset) {
                    err("A document must not include both a" + " \u201Cmeta\u201D element with an" + " \u201Chttp-equiv\u201D attribute" + " whose value is \u201Ccontent-type\u201D," + " and a \u201Cmeta\u201D element with a" + " \u201Ccharset\u201D attribute.");
                }
                if (hasContentTypePragma) {
                    err("A document must not include more than one" + " \u201Cmeta\u201D element with a" + " \u201Chttp-equiv\u201D attribute" + " whose value is \u201Ccontent-type\u201D.");
                }
                hasContentTypePragma = true;
            }
        }
        if ("link" == localName) {
            boolean hasRel = false;
            List<String> relList = new ArrayList<>();
            if (atts.getIndex("", "rel") > -1) {
                hasRel = true;
                // 
                Collections.addAll(// 
                relList, // 
                atts.getValue("", "rel").toLowerCase().split("\\s+"));
            }
            if (atts.getIndex("", "as") > -1 && ((relList != null && !relList.contains("preload") || !hasRel))) {
                err("A \u201Clink\u201D element with an" + " \u201Cas\u201D attribute must have a" + " \u201Crel\u201D attribute that contains the" + " value \u201Cpreload\u201D.");
            }
            if (atts.getIndex("", "integrity") > -1 && ((relList != null && !relList.contains("stylesheet") || !hasRel))) {
                err("A \u201Clink\u201D element with an" + " \u201Cintegrity\u201D attribute must have a" + " \u201Crel\u201D attribute that contains the" + " value \u201Cstylesheet\u201D.");
            }
            if (atts.getIndex("", "sizes") > -1 && ((relList != null && !relList.contains("icon") && !relList.contains("apple-touch-icon")) && !relList.contains("apple-touch-icon-precomposed") || !hasRel)) {
                err("A \u201Clink\u201D element with a" + " \u201Csizes\u201D attribute must have a" + " \u201Crel\u201D attribute that contains the" + " value \u201Cicon\u201D or the value" + " \u201Capple-touch-icon\u201D or the value" + " \u201Capple-touch-icon-precomposed\u201D.");
            }
            if (// 
            atts.getIndex("", "color") > -1 && (!hasRel || (relList != null && !relList.contains("mask-icon")))) {
                err("A \u201Clink\u201D element with a" + " \u201Ccolor\u201D attribute must have a" + " \u201Crel\u201D attribute that contains" + " the value \u201Cmask-icon\u201D.");
            }
            if (// 
            atts.getIndex("", "scope") > -1 && ((relList != null && !relList.contains("serviceworker")) || !hasRel)) {
                err("A \u201Clink\u201D element with a" + " \u201Cscope\u201D attribute must have a" + " \u201Crel\u201D attribute that contains the" + " value \u201Cserviceworker\u201D.");
            }
            if (// 
            atts.getIndex("", "updateviacache") > -1 && ((relList != null && !relList.contains("serviceworker")) || !hasRel)) {
                err("A \u201Clink\u201D element with an" + " \u201Cupdateviacache\u201D attribute must have a" + " \u201Crel\u201D attribute that contains the" + " value \u201Cserviceworker\u201D.");
            }
            if (// 
            atts.getIndex("", "workertype") > -1 && ((relList != null && !relList.contains("serviceworker")) || !hasRel)) {
                err("A \u201Clink\u201D element with a" + " \u201Cworkertype\u201D attribute must have a" + " \u201Crel\u201D attribute that contains the" + " value \u201Cserviceworker\u201D.");
            }
            if ((ancestorMask & BODY_MASK) != 0 && (relList != null && !(relList.contains("dns-prefetch") || relList.contains("pingback") || relList.contains("preconnect") || relList.contains("prefetch") || relList.contains("preload") || relList.contains("prerender") || relList.contains("stylesheet"))) && atts.getIndex("", "itemprop") < 0 && atts.getIndex("", "property") < 0) {
                err("A \u201Clink\u201D element must not appear" + " as a descendant of a \u201Cbody\u201D element" + " unless the \u201Clink\u201D element has an" + " \u201Citemprop\u201D attribute or has a" + " \u201Crel\u201D attribute whose value contains" + " \u201Cdns-prefetch\u201D," + " \u201Cpingback\u201D," + " \u201Cpreconnect\u201D," + " \u201Cprefetch\u201D," + " \u201Cpreload\u201D," + " \u201Cprerender\u201D, or" + " \u201Cstylesheet\u201D.");
            }
        }
        // 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.");
        }
        // having implicit ARIA semantics.
        if (ELEMENTS_WITH_IMPLICIT_ROLE.containsKey(localName) && ELEMENTS_WITH_IMPLICIT_ROLE.get(localName).equals(role)) {
            warn("The \u201C" + role + "\u201D role is unnecessary for" + " element" + " \u201C" + localName + "\u201D.");
        } else if (ELEMENTS_THAT_NEVER_NEED_ROLE.containsKey(localName) && ELEMENTS_THAT_NEVER_NEED_ROLE.get(localName).equals(role)) {
            warn("Element \u201C" + localName + "\u201D does not need a" + " \u201Crole\u201D attribute.");
        } else if ("input" == localName) {
            inputTypeVal = inputTypeVal == null ? "text" : inputTypeVal;
            if (INPUT_TYPES_WITH_IMPLICIT_ROLE.containsKey(inputTypeVal) && INPUT_TYPES_WITH_IMPLICIT_ROLE.get(inputTypeVal).equals(role)) {
                warnExplicitRoleUnnecessaryForType("input", role, inputTypeVal);
            } else if ("email".equals(inputTypeVal) || "search".equals(inputTypeVal) || "tel".equals(inputTypeVal) || "text".equals(inputTypeVal) || "url".equals(inputTypeVal)) {
                if (atts.getIndex("", "list") < 0) {
                    if ("textbox".equals(role)) {
                        warn("The \u201Ctextbox\u201D role is unnecessary" + " for an \u201Cinput\u201D element that" + " has no \u201Clist\u201D attribute and" + " whose type is" + " \u201C" + inputTypeVal + "\u201D.");
                    }
                } else {
                    if ("combobox".equals(role)) {
                        warn("The \u201Ccombobox\u201D role is unnecessary" + " for an \u201Cinput\u201D element that" + " has a \u201Clist\u201D attribute and" + " whose type is" + " \u201C" + inputTypeVal + "\u201D.");
                    }
                }
            }
        } else if (atts.getIndex("", "href") > -1 && "link".equals(role) && ("a".equals(localName) || "area".equals(localName) || "link".equals(localName))) {
            warn("The \u201Clink\u201D role is unnecessary for element" + " \u201C" + localName + "\u201D with attribute" + " \u201Chref\u201D.");
        } else if (("tbody".equals(localName) || "tfoot".equals(localName) || "thead".equals(localName)) && "rowgroup".equals(role)) {
            warn("The \u201Crowgroup\u201D role is unnecessary for element" + " \u201C" + localName + "\u201D.");
        } else if ("th" == localName && ("columnheader".equals(role) || "columnheader".equals(role))) {
            warn("The \u201C" + role + "\u201D role is unnecessary for" + " element \u201Cth\u201D.");
        } else if ("li" == localName && "listitem".equals(role) && !"menu".equals(parentName)) {
            warn("The \u201Clistitem\u201D role is unnecessary for an" + " \u201Cli\u201D element whose parent is" + " an \u201Col\u201D element or a" + " \u201Cul\u201D element.");
        } else if ("button" == localName && "button".equals(role) && "menu".equals(atts.getValue("", "type"))) {
            warnExplicitRoleUnnecessaryForType("button", "button", "menu");
        } else if ("menu" == localName && "toolbar".equals(role) && "toolbar".equals(atts.getValue("", "type"))) {
            warnExplicitRoleUnnecessaryForType("menu", "toolbar", "toolbar");
        } else if ("li" == localName && "listitem".equals(role) && !"menu".equals(parentName)) {
            warn("The \u201Clistitem\u201D role is unnecessary for an" + " \u201Cli\u201D element whose parent is" + " an \u201Col\u201D element or a" + " \u201Cul\u201D element.");
        }
    } 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 (String token : tokens) {
                ariaReferences.add(new IdrefLocator(getDocumentLocator(), token, att));
            }
        }
    }
    allIds.addAll(ids);
    // aria-activedescendant accompanied by aria-owns
    if (activeDescendant != null && !"".equals(activeDescendant)) {
        // "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 ("style" == localName) {
            child.setIsCollectingCharacters(true);
        }
        if ("script" == localName) {
            child.setIsCollectingCharacters(true);
        }
        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 ("article" == localName || "aside" == localName || "nav" == localName || "section" == localName) {
            if (atts.getIndex("", "aria-label") > -1 && !"".equals(atts.getValue("", "aria-label"))) {
                child.setHeadingFound();
            }
        }
        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();
            }
        }
        if (localName.contains("-")) {
            if (atts.getIndex("", "is") > -1) {
                err("Autonomous custom elements must not specify the" + " \u201cis\u201d attribute.");
            }
            try {
                CustomElementName.THE_INSTANCE.checkValid(localName);
            } catch (DatatypeException e) {
                try {
                    if (getErrorHandler() != null) {
                        String msg = e.getMessage();
                        if (e instanceof Html5DatatypeException) {
                            msg = msg.substring(msg.indexOf(": ") + 2);
                        }
                        VnuBadElementNameException ex = new VnuBadElementNameException(localName, uri, msg, getDocumentLocator(), CustomElementName.class, false);
                        getErrorHandler().error(ex);
                    }
                } catch (ClassNotFoundException ce) {
                }
            }
        }
    } else if ("http://n.validator.nu/custom-elements/" == uri) {
        /*
             * For elements with names containing "-" (custom elements), the
             * customelements/NamespaceChanging* code exposes them to jing as
             * elements in the http://n.validator.nu/custom-elements/ namespace.
             * Therefore our RelaxNG schema allows those elements. However,
             * schematronequiv.Assertions still sees those elements as being in
             * the HTML namespace, so here we need to emit an error for the case
             * where, in source transmitted with an XML content type, somebody
             * (for whatever reason) has elements in their markup which they
             * have explicitly placed in that namespace (otherwise, due to
             * allowing those elements in our RelaxNG schema, Jing on its own
             * won't emit any error for them).
             */
        err("Element \u201c" + localName + "\u201d from namespace" + " \u201chttp://n.validator.nu/custom-elements/\u201d" + " not allowed.");
    } else {
        StackNode child = new StackNode(ancestorMask, null, role, activeDescendant, forAttr);
        if (activeDescendant != null) {
            openActiveDescendants.put(child, new LocatorImpl(getDocumentLocator()));
        }
        push(child);
    }
    stack[currentPtr].setLocator(new LocatorImpl(getDocumentLocator()));
}
Also used : Html5DatatypeException(nu.validator.datatype.Html5DatatypeException) HashMap(java.util.HashMap) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) ArrayList(java.util.ArrayList) CssParseException(org.w3c.css.parser.CssParseException) VnuBadElementNameException(nu.validator.checker.VnuBadElementNameException) StyleSheetParser(org.w3c.css.css.StyleSheetParser) Html5DatatypeException(nu.validator.datatype.Html5DatatypeException) DatatypeException(org.relaxng.datatype.DatatypeException) Locator(org.xml.sax.Locator) ImageCandidateStringsWidthRequired(nu.validator.datatype.ImageCandidateStringsWidthRequired) VnuBadAttrValueException(nu.validator.checker.VnuBadAttrValueException) LocatorImpl(nu.validator.checker.LocatorImpl) TaintableLocatorImpl(nu.validator.checker.TaintableLocatorImpl) ArrayList(java.util.ArrayList) List(java.util.List) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet) AutocompleteDetailsUrl(nu.validator.datatype.AutocompleteDetailsUrl) CustomElementName(nu.validator.datatype.CustomElementName) AutocompleteDetailsText(nu.validator.datatype.AutocompleteDetailsText) Color(nu.validator.datatype.Color) AutocompleteDetailsMonth(nu.validator.datatype.AutocompleteDetailsMonth) TaintableLocatorImpl(nu.validator.checker.TaintableLocatorImpl) ByteArrayInputStream(java.io.ByteArrayInputStream) AutocompleteDetailsTel(nu.validator.datatype.AutocompleteDetailsTel) ApplContext(org.w3c.css.util.ApplContext) HashMap(java.util.HashMap) Map(java.util.Map) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) CssError(org.w3c.css.parser.CssError)

Example 2 with LocatorImpl

use of nu.validator.checker.LocatorImpl in project validator by validator.

the class LanguageDetectingChecker method startElement.

/**
 * @see nu.validator.checker.Checker#startElement(java.lang.String,
 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
 */
@Override
public void startElement(String uri, String localName, String name, Attributes atts) throws SAXException {
    if ("html".equals(localName)) {
        htmlStartTagLocator = new LocatorImpl(getDocumentLocator());
        for (int i = 0; i < atts.getLength(); i++) {
            if ("lang".equals(atts.getLocalName(i))) {
                if (request != null) {
                    request.setAttribute("http://validator.nu/properties/lang-found", true);
                }
                htmlElementHasLang = true;
                htmlElementLangAttrValue = atts.getValue(i);
                declaredLangCode = new ULocale(htmlElementLangAttrValue).getLanguage();
            } else if ("dir".equals(atts.getLocalName(i))) {
                hasDir = true;
                dirAttrValue = atts.getValue(i);
            }
        }
    } else if ("body".equals(localName)) {
        inBody = true;
    } else if (inBody) {
        if (currentOpenElementsInDifferentLang > 0) {
            currentOpenElementsInDifferentLang++;
        } else {
            for (int i = 0; i < atts.getLength(); i++) {
                if ("lang".equals(atts.getLocalName(i))) {
                    if (!"".equals(htmlElementLangAttrValue) && !htmlElementLangAttrValue.equals(atts.getValue(i))) {
                        currentOpenElementsInDifferentLang++;
                    }
                }
            }
        }
    }
    if (Arrays.binarySearch(SKIP_NAMES, localName) >= 0) {
        currentOpenElementsWithSkipName++;
    }
}
Also used : ULocale(com.ibm.icu.util.ULocale) LocatorImpl(nu.validator.checker.LocatorImpl)

Aggregations

LocatorImpl (nu.validator.checker.LocatorImpl)2 ULocale (com.ibm.icu.util.ULocale)1 ByteArrayInputStream (java.io.ByteArrayInputStream)1 ArrayList (java.util.ArrayList)1 HashMap (java.util.HashMap)1 HashSet (java.util.HashSet)1 LinkedHashSet (java.util.LinkedHashSet)1 List (java.util.List)1 Map (java.util.Map)1 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)1 TaintableLocatorImpl (nu.validator.checker.TaintableLocatorImpl)1 VnuBadAttrValueException (nu.validator.checker.VnuBadAttrValueException)1 VnuBadElementNameException (nu.validator.checker.VnuBadElementNameException)1 AutocompleteDetailsMonth (nu.validator.datatype.AutocompleteDetailsMonth)1 AutocompleteDetailsTel (nu.validator.datatype.AutocompleteDetailsTel)1 AutocompleteDetailsText (nu.validator.datatype.AutocompleteDetailsText)1 AutocompleteDetailsUrl (nu.validator.datatype.AutocompleteDetailsUrl)1 Color (nu.validator.datatype.Color)1 CustomElementName (nu.validator.datatype.CustomElementName)1 Html5DatatypeException (nu.validator.datatype.Html5DatatypeException)1