/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.validator; import java.util.Arrays; import java.util.List; import java.util.ResourceBundle; import javax.xml.xpath.XPathConstants; import org.apache.commons.collections.ListUtils; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xwiki.validator.ValidationError.Type; import org.xwiki.validator.framework.AbstractDOMValidator; import org.xwiki.validator.framework.NodeListIterable; /** * Validator allowing to validate (X)HTML content against Dutch Web Guidelines. * <p> *"There are internationally recognized agreements for creating web sites, known as 125 quality requirements standards * warrants a significantly better website. The Netherlands government has assembled these international standards in a * quality model called the Web Guidelines. This quality model comprises 125 quality requirements for the benefit of * better websites." * </p> * * @version $Id: f0a7a00436be8d9e912fba4cda9dd5b7d3f68061 $ */ public class DutchWebGuidelinesValidator extends AbstractDOMValidator { /** * Utility selector. */ private static final String CONTENT_TYPE_META_SELECTOR = "//meta[@http-equiv='Content-Type']"; /** * String used to identify the charset in the content-type meta. */ private static final String CONTENT_CHARSET_FRAGMENT = "charset="; /** * Character used to mark the beginning of the query string in a URL. */ private static final String QUERY_STRING_SEPARATOR = "?"; /** * Message resources. */ private ResourceBundle messages = ResourceBundle.getBundle("DutchWebGuidelines"); /** * Constructor. */ public DutchWebGuidelinesValidator() { super(false); } @Override public String getName() { return "Dutch Web Guidelines"; } /** * Add an error to the list of errors using our message resources. * * @param errorType type of the error * @param line line where the error occurred * @param column where the error occurred * @param key key to retrieve the value from in the message properties */ @Override protected void addError(Type errorType, int line, int column, String key) { super.addError(errorType, line, column, this.messages.getString(key)); } @Override protected void validate(Document document) { // RPD 1 validateRpd1s1(); validateRpd1s2(); validateRpd1s3(); // RPD 2 validateRpd2s1(); validateRpd2s2(); validateRpd2s3(); validateRpd2s4(); validateRpd2s5(); validateRpd2s6(); validateRpd2s7(); validateRpd2s8(); validateRpd2s9(); // RPD 3 validateRpd3s1(); validateRpd3s2(); validateRpd3s3(); validateRpd3s4(); validateRpd3s5(); validateRpd3s6(); validateRpd3s7(); validateRpd3s8(); validateRpd3s9(); validateRpd3s10(); validateRpd3s11(); validateRpd3s12(); validateRpd3s13(); validateRpd3s14(); validateRpd3s15(); // RPD 4 validateRpd4s1(); validateRpd4s2(); validateRpd4s3(); validateRpd4s4(); validateRpd4s5(); validateRpd4s6(); validateRpd4s7(); // RPD 5 validateRpd5s1(); // RPD 6 validateRpd6s1(); validateRpd6s2(); // RPD 7 validateRpd7s1(); validateRpd7s2(); validateRpd7s3(); validateRpd7s4(); validateRpd7s5(); validateRpd7s6(); validateRpd7s7(); // RPD 8 validateRpd8s1(); validateRpd8s2(); validateRpd8s3(); validateRpd8s4(); validateRpd8s5(); validateRpd8s6(); validateRpd8s7(); validateRpd8s8(); validateRpd8s9(); validateRpd8s10(); validateRpd8s11(); validateRpd8s12(); validateRpd8s13(); validateRpd8s14(); validateRpd8s15(); validateRpd8s16(); validateRpd8s17(); validateRpd8s18(); validateRpd8s19(); validateRpd8s20(); validateRpd8s21(); validateRpd8s22(); validateRpd8s23(); // RPD 9 validateRpd9s1(); validateRpd9s2(); // RPD 10 validateRpd10s1(); validateRpd10s2(); validateRpd10s3(); // RPD 11 validateRpd11s1(); validateRpd11s2(); validateRpd11s3(); validateRpd11s4(); validateRpd11s5(); validateRpd11s6(); validateRpd11s7(); validateRpd11s8(); validateRpd11s9(); validateRpd11s10(); // RPD 12 validateRpd12s1(); // RPD 13 validateRpd13s1(); validateRpd13s2(); validateRpd13s3(); validateRpd13s4(); validateRpd13s5(); validateRpd13s6(); validateRpd13s7(); validateRpd13s8(); validateRpd13s9(); validateRpd13s10(); validateRpd13s11(); validateRpd13s12(); validateRpd13s13(); validateRpd13s14(); validateRpd13s15(); validateRpd13s16(); validateRpd13s17(); validateRpd13s18(); // RPD 14 validateRpd14s1(); // RPD 15 validateRpd15s1(); validateRpd15s2(); validateRpd15s3(); validateRpd15s4(); validateRpd15s5(); validateRpd15s6(); validateRpd15s7(); // RPD 16 validateRpd16s1(); validateRpd16s2(); validateRpd16s3(); validateRpd16s4(); // RPD 18 validateRpd18s1(); validateRpd18s2(); // RPD 22 validateRpd22s1(); validateRpd22s2(); validateRpd22s3(); validateRpd22s4(); validateRpd22s5(); validateRpd22s6(); validateRpd22s7(); validateRpd22s8(); validateRpd22s9(); validateRpd22s10(); } /** * Keep structure and design separate as much as possible: use HTML or XHTML for the structure of the site and CSS * for its design. */ public void validateRpd1s1() { // HTML Validation errors are checked by XHTMLValidator. } /** * Build websites according to the "layered construction" principle. */ public void validateRpd1s2() { // This guideline cannot be automatically tested. } /** * Do not make the function of the website dependent on optional technology, such as CSS and client-side script: * optional technology should complement the information on the site and its use, and should not interfere with * access to it if this technology is not supported. */ public void validateRpd1s3() { // Links validation. NodeListIterable linkElements = getElements("a"); // Links must not use javascript in href. for (String hrefValue : getAttributeValues(linkElements, "href")) { assertFalse(Type.ERROR, "rpd1s3.javascript", hrefValue.startsWith("javascript:")); } // Links must not use the attributes listed below. List<String> forbiddenAttributes = Arrays.asList(ATTR_BLUR, ATTR_CHANGE, ATTR_CLICK, ATTR_FOCUS, ATTR_LOAD, ATTR_MOUSEOVER, ATTR_SELECT, ATTR_SELECT, ATTR_UNLOAD); for (Node linkElement : linkElements) { if (!ListUtils.intersection(getAttributeNames(linkElement), forbiddenAttributes).isEmpty()) { assertFalse(Type.ERROR, "rpd1s3.inlineEventHandlers", getAttributeValue(linkElement, ATTR_HREF).equals( "") || getAttributeValue(linkElement, ATTR_HREF).equals("#")); } } // Form validation NodeListIterable formElements = getElements("form"); for (Node formElement : formElements) { // Look for either a submit input or an image input with the 'alt' attribute specified. // See http://www.w3.org/TR/WCAG10-HTML-TECHS/#forms-graphical-buttons String inputSubmitOrImage = "//input[@type = 'submit' or (@type = 'image' and @alt != '')]"; // The default value of the type attribute of a button element is 'submit'. // See http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_button String buttonSubmit = "//button[not(@type) or @type = 'submit']"; assertTrue(Type.ERROR, "rpd1s3.formSubmit", (Boolean) evaluate(formElement, inputSubmitOrImage + " | " + buttonSubmit, XPathConstants.BOOLEAN)); } } /** * Use HTML 4.01 or XHTML 1.0 according to the W3C specifications for the markup of websites. */ public void validateRpd2s1() { // HTML Validation errors are checked by XHTMLValidator. } /** * Do not use any markup which is referred to as deprecated (outmoded) in the W3C specifications. */ public void validateRpd2s2() { // HTML Validation errors are checked by XHTMLValidator. } /** * When modifying an existing website: only use the Transitional version of HTML 4.01 or XHTML 1.0 if it is not * possible or desirable to use the Strict version. */ public void validateRpd2s3() { // This guideline cannot be automatically tested, however we check that a DOCTYPE has been specified. assertFalse(Type.ERROR, "rpd2s3.noDoctype", this.document.getDoctype() == null); } /** * When building a new website: only use the Strict version of HTML 4.01 or XHTML 1.0. */ public void validateRpd2s4() { // This guideline cannot be automatically tested, however we check that a DOCTYPE has been specified. assertFalse(Type.ERROR, "rpd2s4.noDoctype", this.document.getDoctype() == null); } /** * Do not use frames on websites. Therefore, also do not use the Frameset version of HTML 4.01 or XHTML 1.0. */ public void validateRpd2s5() { // Usage of frameset doctype is forbidden if (this.document.getDoctype() != null) { assertFalse(Type.ERROR, "rpd2s5.framesetDoctype", StringUtils.containsIgnoreCase(this.document.getDoctype() .getPublicId(), "frameset")); } // Usage of frameset is forbidden assertFalse(Type.ERROR, "rpd2s5.frameset", containsElement(ELEM_FRAMESET)); // Usage of frames is forbidden assertFalse(Type.ERROR, "rpd2s5.frame", containsElement(ELEM_FRAME)); } /** * Use CSS Level-2.1 according to the W3C specification for designing websites. */ public void validateRpd2s6() { // CSS Validation errors are checked by CSSValidator. } /** * If client-side script is used, use ECMAScript according to the specification. */ public void validateRpd2s7() { // This guideline cannot be automatically tested. } /** * If elements in the HTML hierarchy are to be manipulated, use the W3C DOM according to the specification. */ public void validateRpd2s8() { // This guideline cannot be automatically tested. } /** * Build a website according to the Web Content Accessibility Guidelines (WCAG 1.0) of the W3C. */ public void validateRpd2s9() { // This guideline cannot be automatically tested. } /** * Write both grammatically correct and descriptive markup. */ public void validateRpd3s1() { // <b> and <i> are not allowed. assertFalse(Type.ERROR, "rpd3s1.boldMarkup", containsElement("b")); assertFalse(Type.ERROR, "rpd3s1.italicMarkup", containsElement("i")); } /** * Use markup for headings that express the hierarchy of information on the page. */ public void validateRpd3s2() { NodeListIterable h1s = getElements(ELEM_H1); // A page must contain at least a h1. assertTrue(Type.ERROR, "rpd3s2.noheading", h1s.getNodeList().getLength() > 0); // It is recommended to use only one h1 per page. if (h1s.getNodeList().getLength() > 1) { addError(Type.WARNING, -1, -1, "rpd3s2.multipleh1"); } } /** * Do not skip any levels in the hierarchy of headings in the markup. */ public void validateRpd3s3() { List<String> headings = Arrays.asList("h1", "h2", "h3", "h4", "h5", "h6"); int previousLevel = 1; for (Node element : getElements(headings)) { int currentLevel = Integer.parseInt(element.getNodeName().substring(1)); // Verify that we haven't jumped from h1 to h3. assertTrue(Type.ERROR, "rpd3s3.headings", currentLevel <= previousLevel + 1); previousLevel = currentLevel; } } /** * Use the p (paragraph) element to indicate paragraphs. Do not use the br (linebreak) element to separate * paragraphs. */ public void validateRpd3s4() { for (Node br : getElements(ELEM_BR)) { Node currentNode = br.getNextSibling(); while (currentNode != null && currentNode.getNodeType() == Node.TEXT_NODE && StringUtils.isBlank(currentNode.getTextContent())) { // Ignore white spaces between <br/>. currentNode = currentNode.getNextSibling(); } if (currentNode != null) { assertFalse(Type.ERROR, "rpd3s4.linebreaks", currentNode.getNodeName().equals(ELEM_BR)); } } } /** * Use the em (emphasis) and strong elements to indicate emphasis. */ public void validateRpd3s5() { // <b> and <i> are not allowed. String key = "rpd3s5.invalidMarkup"; assertFalse(Type.ERROR, key, containsElement(ELEM_BOLD)); assertFalse(Type.ERROR, key, containsElement(ELEM_ITALIC)); } /** * Use the abbr (abbreviation) element for an abbreviation if confusion could arise concerning its meaning, if the * abbreviation plays a very important role in the text or if the abbreviation is not listed in the Dutch * dictionary. */ public void validateRpd3s6() { // This guideline cannot be automatically tested. } /** * Use the dfn (definition) element to indicate terms that are defined elsewhere in a definition list. */ public void validateRpd3s7() { // This guideline cannot be automatically tested. } /** * Use the ins (insertion) and del (deletion) elements to indicate regular changes in the content of a page. */ public void validateRpd3s8() { // This guideline cannot be automatically tested. } /** * Avoid using the sup (superscript) and sub (subscript) element if possible. */ public void validateRpd3s9() { // <sub> and <sup> are not allowed. assertFalse(Type.ERROR, "rpd3s9.sub", containsElement("sub")); assertFalse(Type.ERROR, "rpd3s9.sup", containsElement("sup")); } /** * Use the cite element for references to people and titles. */ public void validateRpd3s10() { // This guideline cannot be automatically tested. } /** * Avoid using the q (quotation) element. */ public void validateRpd3s11() { // <q> is not allowed. assertFalse(Type.ERROR, "rpd3s11.quotation", containsElement("q")); } /** * Use the blockquote element to indicate (long) quotations. */ public void validateRpd3s12() { // This guideline cannot be automatically tested. } /** * Use ol (ordered list) and ul (unordered list) elements to indicate lists. */ public void validateRpd3s13() { for (Node br : getElements(ELEM_BR)) { Node previousNode = null; String regex = "^\\s*(\\*|-|[0-9]\\.).*"; for (Node currentNode : new NodeListIterable(br.getParentNode().getChildNodes())) { Node nextNode = currentNode.getNextSibling(); if (previousNode != null && nextNode != null) { boolean currentNodeMatches = currentNode.getNodeName().equals(ELEM_BR); boolean previousNodeMatches = previousNode.getNodeType() == Node.TEXT_NODE && previousNode.getTextContent().matches(regex); boolean nextNodeMatches = nextNode.getNodeType() == Node.TEXT_NODE && nextNode.getTextContent().matches(regex); assertFalse(Type.ERROR, "rpd3s13.lists", previousNodeMatches && currentNodeMatches && nextNodeMatches); } previousNode = currentNode; } } } /** * Use the dl (definition list), the dt (definition term) and dd (definition data) elements to indicate lists with * definitions. */ public void validateRpd3s14() { // This guideline cannot be automatically tested. } /** * Give meaningful names to id and class attributes. */ public void validateRpd3s15() { // This guideline cannot be automatically tested. } /** * Create unique, unchanging URLs. */ public void validateRpd4s1() { // URL guidelines can't be automatically tested, this validator allow to validate content only. } /** * Dynamically generated URLs should continue to refer to the same content if content is changed or added. */ public void validateRpd4s2() { // URL guidelines can't be automatically tested, this validator allow to validate content only. } /** * Avoid using sessions in URLs. */ public void validateRpd4s3() { // URL guidelines can't be automatically tested, this validator allow to validate content only. } /** * Provide redirection to the new location if information is moved. */ public void validateRpd4s4() { // URL guidelines can't be automatically tested, this validator allow to validate content only. } /** * Automatic redirection should be carried by the server if possible. */ public void validateRpd4s5() { // URL guidelines can't be automatically tested, this validator allow to validate content only. } /** * Use friendly URLs that are readable and recognizable. */ public void validateRpd4s6() { // URL guidelines can't be automatically tested, this validator allow to validate content only. } /** * Set up a readable, expandable directory structure. */ public void validateRpd4s7() { // URL guidelines can't be automatically tested, this validator allow to validate content only. } /** * In the event that important information is provided through a closed standard, the same information should also * be provided through an open standard. */ public void validateRpd5s1() { // This guideline cannot be automatically tested. } /** * Each HTML or XHTML document must begin with a valid doctype declaration. */ public void validateRpd6s1() { assertTrue(Type.ERROR, "rpd6s1.doctype", StringUtils.containsIgnoreCase(this.document.getDoctype() .getPublicId(), ELEM_HTML)); } /** * Put the content of the page in the HTML source code in order of importance. */ public void validateRpd6s2() { // This guideline cannot be automatically tested. } /** * The alt (alternative) attribute should be used on every img (image) and area element and should be provided with * an effective alternative text. */ public void validateRpd7s1() { // alt attributes are mandatory in <img> for (Node img : getElements(ELEM_IMG)) { assertTrue(Type.ERROR, "rpd7s1.img", hasAttribute(img, ATTR_ALT)); } // alt attributes are mandatory in <area> for (Node area : getElements(ELEM_AREA)) { assertTrue(Type.ERROR, "rpd7s1.area", hasAttribute(area, ATTR_ALT)); } // alt attributes are mandatory in <input type="image"> for (Node input : getElements(ELEM_INPUT)) { if (getAttributeValue(input, ATTR_TYPE).equals(IMAGE)) { assertTrue(Type.ERROR, "rpd7s1.input", hasAttribute(input, ATTR_ALT)); } } } /** * Do not use an alt attribute to display tooltips. */ public void validateRpd7s2() { // This guideline cannot be automatically tested. } /** * Do not use d-links on websites. Use of the longdesc (long description) attribute is preferred if the text * alternative on the alt attribute is inadequate for understanding the information in the image. */ public void validateRpd7s3() { // This guideline cannot be automatically tested. } /** * Images placed in a link should have a non-empty text alternative to enable visitors who do not see the image to * follow the link. */ public void validateRpd7s4() { for (Node link : getElements(ELEM_LINK)) { // Look for images in the link. boolean hasNonEmptyAlt = false; for (Node child : getChildren(link, ELEM_IMG)) { if (StringUtils.isNotEmpty(getAttributeValue(child, ATTR_ALT))) { hasNonEmptyAlt = true; } } // Look for text in the link. boolean hasText = false; for (Node linkChild : new NodeListIterable(link.getChildNodes())) { if (linkChild.getNodeType() == Node.TEXT_NODE) { hasText = true; } } // Images in links must have a not empty alt attribute if there's no text in the link. assertTrue(Type.ERROR, "rpd7s4.links", hasNonEmptyAlt || hasText); } } /** * When using image maps, indicate an effective text alternative for both the img element and each area element by * means of the alt attribute. */ public void validateRpd7s5() { // Non-empty alt attributes are mandatory in <img usemap=''> for (Node img : getElements(ELEM_IMG)) { if (hasAttribute(img, "usemap")) { assertFalse(Type.ERROR, "rpd7s5.img", StringUtils.isEmpty(getAttributeValue(img, ATTR_ALT))); } } // Non-empty alt attributes are mandatory in <area> for (Node area : getElements(ELEM_AREA)) { assertFalse(Type.ERROR, "rpd7s5.area", StringUtils.isEmpty(getAttributeValue(area, ATTR_ALT))); } } /** * Decorative images should be inserted via CSS as much as possible. Informative images should be inserted via HTML. */ public void validateRpd7s6() { // This guideline cannot be automatically tested. } /** * Applying CSS Image Replacement techniques to essential information is not recommended. */ public void validateRpd7s7() { // This guideline cannot be automatically tested. } /** * Do not describe the mechanism behind following a link. */ public void validateRpd8s1() { List<String> forbiddenLinkTexts = Arrays.asList(messages.getString("rpd8s1.forbiddenLinkTexts").split(",")); for (Node link : getElements(ELEM_LINK)) { for (Node linkChild : new NodeListIterable(link.getChildNodes())) { if (linkChild.getNodeType() == Node.TEXT_NODE) { for (String forbiddenLinkText : forbiddenLinkTexts) { assertFalse(Type.ERROR, "rpd8s1.link", StringUtils.containsIgnoreCase(linkChild .getTextContent(), forbiddenLinkText)); } } } } } /** * Write clear, descriptive text for links. */ public void validateRpd8s2() { // This guideline cannot be automatically tested. } /** * Use the minimum amount of text needed to understand where the link leads. */ public void validateRpd8s3() { // This guideline cannot be automatically tested. } /** * Provide sufficient information on the destination of a link to prevent unpleasant surprises for the visitor. */ public void validateRpd8s4() { // This guideline cannot be automatically tested. } /** * When using client-side script in combination with a link: make the script functionality an expansion of the basic * functionality of the link. */ public void validateRpd8s5() { // Already checked by RPD 1s3 } /** * When using client-side script in combination with a link: if the link does not lead to anything, do not confront * the visitor without support for client-side script with a non-working link. */ public void validateRpd8s6() { // Already checked by RPD 1s3 } /** * When using client-side script in combination with a link: if necessary, use client-side script as an expansion of * server-side functions. */ public void validateRpd8s7() { // This guideline cannot be automatically tested. } /** * Links must be easy to distinguish from other text. */ public void validateRpd8s8() { // This guideline cannot be automatically tested. } /** * Provide a logical order for the links on the page. Use the tabindex attribute to deviate from the standard tab * order for links if this order is inadequate for correct use of the page by keyboard users. */ public void validateRpd8s9() { // This guideline cannot be automatically tested. } /** * Do not make it impossible to tab to links. Do not remove the focus rectangle surrounding a link or the * possibility of focusing on a link. */ public void validateRpd8s10() { // This guideline cannot be automatically tested. } /** * Avoid using the Access key attribute. If the decision is nevertheless made to apply this attribute, only use it * on links that remain unchanged throughout the site (e.g. main navigation) and limit the shortcut key combinations * to numbers. */ public void validateRpd8s11() { for (Node link : getElements(ELEM_LINK)) { if (hasAttribute(link, ATTR_ACCESSKEY)) { assertTrue(Type.ERROR, "rpd8s11.accesskey", StringUtils.isNumeric(getAttributeValue(link, ATTR_ACCESSKEY))); } } } /** * Give blind visitors additional options to skip long lists of links. */ public void validateRpd8s12() { // This guideline cannot be automatically tested. } /** * At the top of pages with many topics, provide a page index with links to navigate to the different topics. */ public void validateRpd8s13() { // This guideline cannot be automatically tested. } /** * Links on websites should not automatically open new windows without warning. */ public void validateRpd8s14() { for (Node link : getElements(ELEM_LINK)) { // target attribute is forbidden assertFalse(Type.ERROR, "rpd8s14.target", hasAttribute(link, "target")); if (hasAttribute(link, ATTR_CLICK)) { // Usage of window.open is forbidden assertFalse(Type.ERROR, "rpd8s14.window", getAttributeValue(link, ATTR_CLICK).contains("window.open")); } } } /** * Do not open any new windows automatically, unless the location of the link contains useful information that may * be necessary during an important uninterruptible process. */ public void validateRpd8s15() { // This guideline cannot be automatically tested. } /** * Links to e-mail addresses: the e-mail address to which the message is addressed must be visible in the link text. */ public void validateRpd8s16() { for (Node link : getElements(ELEM_LINK)) { String href = getAttributeValue(link, ATTR_HREF); if (href != null && href.startsWith(MAILTO)) { String email = StringUtils.substringAfter(href, MAILTO); if (email.contains(QUERY_STRING_SEPARATOR)) { email = StringUtils.substringBefore(email, QUERY_STRING_SEPARATOR); } assertTrue(Type.ERROR, "rpd8s16.email", link.getTextContent().contains(email)); } } } /** * Links to e-mail addresses: the URL in the href attribute of a link to an e-mail address may only contain the * mailto protocol and an e-mail address. */ public void validateRpd8s17() { for (Node link : getElements(ELEM_LINK)) { String href = getAttributeValue(link, ATTR_HREF); if (href != null && href.startsWith(MAILTO)) { String email = StringUtils.substringAfter(href, MAILTO); assertTrue(Type.ERROR, "rpd8s17.email", email .matches("^[\\w\\-]([\\.\\w])+[\\w]+@([\\w\\-]+\\.)+[a-zA-Z]{2,4}$")); } } } /** * Do not apply any technical measures to the website to hide an e-mail address from spam robots. */ public void validateRpd8s18() { // This guideline cannot be automatically tested. } /** * Be extremely cautious when publishing e-mail addresses of visitors to the website. Inform the visitor of which * information will be published on the site, or do not publish the visitor's e-mail address. */ public void validateRpd8s19() { // This guideline cannot be automatically tested. } /** * When presenting downloadable files, inform the visitor how to download and then use them. */ public void validateRpd8s20() { // This guideline cannot be automatically tested. } /** * Serve files with the correct MIME type. */ public void validateRpd8s21() { // This guideline cannot be automatically tested. } /** * Do not automatically open links to downloadable files in a new window. */ public void validateRpd8s22() { // Duplicate of 8s14 } /** * Do not intentionally serve downloadable files with an unknown or incorrect MIME type to force the browser to do * something. */ public void validateRpd8s23() { // This guideline cannot be automatically tested. } /** * CSS should be placed in linked files and not mixed with the HTML source code. */ public void validateRpd9s1() { String exprString = "//@style"; assertFalse(Type.ERROR, "rpd9s1.attr", ((Boolean) evaluate(getElement(ELEM_BODY), exprString, XPathConstants.BOOLEAN))); assertFalse(Type.ERROR, "rpd9s1.tag", getChildren(getElement(ELEM_BODY), "style").getNodeList().getLength() > 0); } /** * Pages should remain usable if a web browser does not support CSS. */ public void validateRpd9s2() { // This guideline cannot be automatically tested. } /** * Make sure that the meaning of communicative elements is not expressed only through colour. */ public void validateRpd10s1() { // This guideline cannot be automatically tested. } /** * Be consistent with colour use when indicating meaning. */ public void validateRpd10s2() { // This guideline cannot be automatically tested. } /** * Make sure there is sufficient brightness contrast between the text and the background colour. */ public void validateRpd10s3() { // This guideline cannot be automatically tested. } /** * Use tables to display relational information and do not use them for layout. */ public void validateRpd11s1() { // This guideline cannot be automatically tested. } /** * Use the th (table header) to describe a column or row in a table with relational information. */ public void validateRpd11s2() { for (Node table : getElements(ELEM_TABLE)) { assertTrue(Type.ERROR, "rpd11s2.th", getChildrenTagNames(table).contains(ELEM_TH)); } } /** * Group rows with only th (table header) cells with the thead (table head) element. Group the rest of the table * with the tbody (table body) element. */ public void validateRpd11s3() { // This guideline cannot be automatically tested. } /** * @param table Table to analyze * @return true if the table contains th with ids and td */ private boolean hasTableHeadersAndIds(Node table) { boolean hasHeadersAndIds = false; String exprString = "//td[@headers]"; hasHeadersAndIds = (Boolean) evaluate(table, exprString, XPathConstants.BOOLEAN); exprString = "//th[@id]"; hasHeadersAndIds = hasHeadersAndIds && (Boolean) evaluate(table, exprString, XPathConstants.BOOLEAN); return hasHeadersAndIds; } /** * Use the scope attribute to associate table labels (th cells) with columns or rows. */ public void validateRpd11s4() { for (Node table : getElements(ELEM_TABLE)) { boolean hasHeadersAndIds = hasTableHeadersAndIds(table); if (!hasHeadersAndIds) { for (Node th : getChildren(table, ELEM_TH)) { assertTrue(Type.ERROR, "rpd11s4.scope", hasAttribute(th, ATTR_SCOPE)); } } } } /** * Use the headers and id attributes to associate table labels (th cells) with individual cells in complex tables. */ public void validateRpd11s5() { for (Node table : getElements(ELEM_TABLE)) { boolean hasScope = false; for (Node th : getChildren(table, ELEM_TH)) { if (hasAttribute(th, ATTR_SCOPE)) { hasScope = true; } } if (!hasScope) { assertTrue(Type.ERROR, "rpd11s5.headers", hasTableHeadersAndIds(table)); } } } /** * Provide abbreviations for table labels (th cells) by means of the abbr (abbreviation) attribute if the content of * the table label is so long that repetition in a speech browser could cause irritation. */ public void validateRpd11s6() { // This guideline cannot be automatically tested. } /** * Use the caption element or heading markup to provide a heading above a table. */ public void validateRpd11s7() { // This guideline cannot be automatically tested. } /** * When modifying an existing website: use CSS for the presentation and layout of web pages, and avoid using tables * for layout. */ public void validateRpd11s8() { // This guideline cannot be automatically tested. } /** * When using tables for layout: do not use more than one table and use CSS for the design of this table as much as * possible. */ public void validateRpd11s9() { // This guideline cannot be automatically tested. } /** * When using tables for layout: do not apply any accessibility markup. */ public void validateRpd11s10() { // This guideline cannot be automatically tested. } /** * Do not use frames on websites. This applies to regular frames in framesets as well as iframes. */ public void validateRpd12s1() { // Usage of iframes is forbidden // frameset and frame tags are checked in RPD2.5. assertFalse(Type.ERROR, "rpd12s1.iframe", containsElement(ELEM_IFRAME)); } /** * Use the label element to explicitly associate text with an input field in a form. */ public void validateRpd13s1() { String message = "rpd13s1.label"; // type = text|password|checkbox|radio|submit|reset|file|hidden|image|button List<String> inputWithoutLabels = Arrays.asList(SUBMIT, RESET, IMAGE, BUTTON, HIDDEN); for (Node input : getElements(ELEM_INPUT)) { // Some inputs doesn't need a label. if (!inputWithoutLabels.contains(getAttributeValue(input, ATTR_TYPE))) { // Labelled inputs must have an ID. String id = getAttributeValue(input, ATTR_ID); assertFalse(Type.ERROR, message, id == null); if (id != null) { // Looking for the label associated to the input. String exprString = "//label[@for='" + id + "']"; assertTrue(Type.ERROR, message, (Boolean) evaluate(this.document, exprString, XPathConstants.BOOLEAN)); } } } } /** * Use the tabindex attribute to deviate from the standard tab order on form fields if this order is inadequate for * correct use of the form by keyboard users. */ public void validateRpd13s2() { // This guideline cannot be automatically tested. } /** * Apply grouping of input fields by means of the fieldset element. */ public void validateRpd13s3() { // Display a warning if a form without fieldset is present. for (Node form : getElements(ELEM_FORM)) { if (!getChildrenTagNames(form).contains(ELEM_FIELDSET)) { addError(Type.WARNING, -1, -1, "rpd13s3.fieldset"); } } } /** * Avoid automatic redirection during interaction with forms. */ public void validateRpd13s4() { for (Node form : getElements(ELEM_FORM)) { boolean hasSubmit = false; boolean hasDynamicSelect = false; String exprString = "//input[@type='submit']"; hasSubmit = (Boolean) evaluate(form, exprString, XPathConstants.BOOLEAN); exprString = "//input[@type='image']"; hasSubmit = hasSubmit || (Boolean) evaluate(this.document, exprString, XPathConstants.BOOLEAN); assertTrue(Type.ERROR, "rpd13s4.submit", hasSubmit); exprString = "//select[@onchange]"; hasDynamicSelect = (Boolean) evaluate(form, exprString, XPathConstants.BOOLEAN); if (hasDynamicSelect) { addError(Type.WARNING, -1, -1, "rpd13s4.select"); } } } /** * Do not use client-side script or forms as the only way of accessing information on the site. */ public void validateRpd13s5() { for (Node form : getElements(ELEM_FORM)) { // Display a warning if the form has a "onsubmit" event handler. if (hasAttribute(form, ATTR_SUBMIT)) { addError(Type.WARNING, -1, -1, "rpd13s5.onsubmit"); } // Display a warning if the form has a "onchange" event handler. if (hasAttribute(form, ATTR_CHANGE)) { addError(Type.WARNING, -1, -1, "rpd13s5.onchange"); } } } /** * Do not confront a visitor with a non-working form if optional technologies "such as CSS or client-side script" * are not supported by the browser. */ public void validateRpd13s6() { // This guideline cannot be automatically tested. } /** * Use CSS sparingly for input fields and form buttons. */ public void validateRpd13s7() { // This guideline cannot be automatically tested. } /** * If a visitor has to provide personal data, let him know what will be done with this data, e.g. in the form of a * privacy statement. */ public void validateRpd13s8() { // This guideline cannot be automatically tested. } /** * Do not ask a visitor to provide more information by means of a form than necessary for the purpose of the form. * Keep forms as short as possible and limit the mandatory completion of form fields. */ public void validateRpd13s9() { // This guideline cannot be automatically tested. } /** * Indicate which fields are mandatory and which are optional. */ public void validateRpd13s10() { // This guideline cannot be automatically tested. } /** * Provide alternate contact options, such as address details, telephone number or e-mail addresses, if available. */ public void validateRpd13s11() { // This guideline cannot be automatically tested. } /** * Let the visitor know what will be done with the form when it is sent. */ public void validateRpd13s12() { // This guideline cannot be automatically tested. } /** * Give the visitor the option of saving his reply. */ public void validateRpd13s13() { // This guideline cannot be automatically tested. } /** * Once the visitor has completed and sent the form, send him confirmation that his message has been received by the * recipient (autoreply). */ public void validateRpd13s14() { // This guideline cannot be automatically tested. } /** * Before displaying complex forms, give the visitor an impression of the size of the form. */ public void validateRpd13s15() { // This guideline cannot be automatically tested. } /** * List documents which the visitor might need while completing the form beforehand. */ public void validateRpd13s16() { // This guideline cannot be automatically tested. } /** * Provide forms with instructions for the visitor if necessary, particularly for the applicable input fields. */ public void validateRpd13s17() { // This guideline cannot be automatically tested. } /** * Do not add any reset buttons to forms. */ public void validateRpd13s18() { String exprString = "//input[@type='reset']"; assertFalse(Type.ERROR, "rpd13s18.reset", (Boolean) evaluate(this.document, exprString, XPathConstants.BOOLEAN)); } /** * Do not use client-side script for essential functionality on web pages, unless any lack of support for these * scripts is sufficiently compensated by HTML alternatives and/or server-side script. */ public void validateRpd14s1() { // This guideline cannot be automatically tested. } /** * The visitor should have the option of choosing between languages on every page of the site. */ public void validateRpd15s1() { // This guideline cannot be automatically tested. } /** * Links for language choice should have a clear and consistent place in the navigation of the site. */ public void validateRpd15s2() { // This guideline cannot be automatically tested. } /** * Use fully written out (textual) links to the language versions. */ public void validateRpd15s3() { // This guideline cannot be automatically tested. } /** * Write links to language versions in their corresponding languages. */ public void validateRpd15s4() { // This guideline cannot be automatically tested. } /** * Do not use associations with nationalities for language choice. */ public void validateRpd15s5() { // This guideline cannot be automatically tested. } /** * Specify the base language of a page in the markup. */ public void validateRpd15s6() { Node html = getElement(ELEM_HTML); // Check for lang attribute in th html node. assertTrue(Type.ERROR, "rpd15s6.lang", html != null && hasAttribute(html, "lang")); } /** * Indicate language variations in the content of pages in the markup. */ public void validateRpd15s7() { // This guideline cannot be automatically tested. } /** * Specify the character set for web pages. */ public void validateRpd16s1() { NodeListIterable metas = new NodeListIterable((NodeList) evaluate(this.document, CONTENT_TYPE_META_SELECTOR, XPathConstants.NODESET)); assertTrue(Type.ERROR, "rpd16s1.nometa", metas.getNodeList().getLength() > 0); for (Node meta : metas) { assertTrue(Type.ERROR, "rpd16s1.charset", StringUtils.containsIgnoreCase(getAttributeValue(meta, ATTR_CONTENT), CONTENT_CHARSET_FRAGMENT)); } } /** * Specify the UTF-8 character set. */ public void validateRpd16s2() { NodeListIterable metas = new NodeListIterable((NodeList) evaluate(this.document, CONTENT_TYPE_META_SELECTOR, XPathConstants.NODESET)); assertTrue(Type.ERROR, "rpd16s2.nometa", metas.getNodeList().getLength() > 0); for (Node meta : metas) { String content = getAttributeValue(meta, ATTR_CONTENT); assertTrue(Type.ERROR, "rpd16s2.notutf8", StringUtils.containsIgnoreCase(content, "charset=utf-8")); assertTrue(Type.ERROR, "rpd16s2.differs", StringUtils.containsIgnoreCase(content, CONTENT_CHARSET_FRAGMENT + this.document.getXmlEncoding())); } } /** * Also specify the character set by means of HTTP headers, if possible. */ public void validateRpd16s3() { // This guideline cannot be automatically tested. } /** * Use (at least) the meta element to specify the character set and place this element as high as possible in the * head section of the markup. */ public void validateRpd16s4() { Node meta = getElement(ELEM_META); assertTrue(Type.ERROR, "rpd16s4.position", hasAttribute(meta, ATTR_CONTENT) && StringUtils.containsIgnoreCase(getAttributeValue(meta, ATTR_CONTENT), CONTENT_CHARSET_FRAGMENT)); } /** * Use a unique, descriptive title for each page. */ public void validateRpd18s1() { // This guideline cannot be automatically tested. } /** * Write short, concise text, in which the main message is mentioned at the top of the page. */ public void validateRpd18s2() { // This guideline cannot be automatically tested. } /** * Use language that the visitor understands: limit the use of jargon, difficult terms and abbreviations. */ public void validateRpd22s1() { // This guideline cannot be automatically tested. } /** * Give visitors an <em>escape route</em>: possibilities to continue if they get stuck. Escape routes include useful * links, being able to use the back button, a search function, and being able to correct input errors immediately. */ public void validateRpd22s2() { // This guideline cannot be automatically tested. } /** * Don't make visitors guess: provide information on how they can correct errors they have made. Take into account * the most common errors. */ public void validateRpd22s3() { // This guideline cannot be automatically tested. } /** * Make modified error pages "for errors such as dead links (404 Not Found)" where the visitor is given options for * continuing within the site. */ public void validateRpd22s4() { // This guideline cannot be automatically tested. } /** * In the event of an error message as a result of sending a form, give the visitor the option of correcting the * error in the form immediately and don't make him be dependent on the use of the back button. */ public void validateRpd22s5() { // This guideline cannot be automatically tested. } /** * When implementing a search engine on the website: use "smart" search technology that takes into account spelling * errors, similar search terms, terms in singular or plural form, etc. */ public void validateRpd22s6() { // This guideline cannot be automatically tested. } /** * Provide a well-organised list of the most relevant search results. If too many search results are provided, it * takes visitors too long to find the desired information. Give visitors the option of entering search criteria, or * sorting the search results. */ public void validateRpd22s7() { // This guideline cannot be automatically tested. } /** * Give visitors the option of reporting errors on the site. */ public void validateRpd22s8() { // This guideline cannot be automatically tested. } /** * Use colours, icons and textual explanations to draw the visitor's attention to an error message and explain the * problem. */ public void validateRpd22s9() { // This guideline cannot be automatically tested. } /** * Give visitors the option of finding information in alternate ways. For example, by providing a sitemap, search * functions, or by means of a request by e-mail, letter or telephone. */ public void validateRpd22s10() { // This guideline cannot be automatically tested. } }