Permalink
Browse files

Merge pull request #60 from lwright-sq/master

Pull of various commits including Sec.508 support, issue #57
  • Loading branch information...
2 parents 394f99d + 6ddc3c4 commit 7a2678c79c45a946aeef41951f91f826431fdbae Luke Wright committed Jan 8, 2013
@@ -479,9 +479,11 @@
#HTMLCS-wrapper .HTMLCS-issue-source-inner-u2p {
padding: 0.35em;
clear: both;
+ color: white;
}
#HTMLCS-wrapper .HTMLCS-issue-source-not-supported {
+ color: white;
font-size: 1em;
line-height: 1.4em;
margin: 0 0 0.5em 0.2em;
View
@@ -486,7 +486,7 @@ var HTMLCSAuditor = new function()
useStandardSelect.id = _prefix + 'settings-use-standard-select';
useStandardSelect.innerHTML = '';
- var standards = ['WCAG2AAA', 'WCAG2AA', 'WCAG2A'];
+ var standards = HTMLCSAuditor.getStandardList();
for (var i = 0; i < standards.length; i++) {
var standard = standards[i];
var option = _doc.createElement('option');
@@ -725,27 +725,12 @@ var HTMLCSAuditor = new function()
}
var buildMessageDetail = function(id, message, standard) {
- var typeText = '';
-
- var principles = {
- 'Principle1': {
- name: 'Perceivable',
- link: 'http://www.w3.org/TR/WCAG20/#perceivable'
- },
- 'Principle2': {
- name: 'Operable',
- link: 'http://www.w3.org/TR/WCAG20/#operable'
- },
- 'Principle3': {
- name: 'Understandable',
- link: 'http://www.w3.org/TR/WCAG20/#understandable'
- },
- 'Principle4': {
- name: 'Robust',
- link: 'http://www.w3.org/TR/WCAG20/#robust'
- }
+ if (standard === undefined) {
+ standard = _standard;
}
+ var typeText = '';
+
switch (message.type) {
case HTMLCS.ERROR:
typeText = 'Error';
@@ -765,14 +750,11 @@ var HTMLCSAuditor = new function()
}//end switch
var typeClass = _prefix + typeText.toLowerCase();
- var msgCodeParts = message.code.split('.', 5);
- var principle = msgCodeParts[1];
- var techniques = msgCodeParts[4].split(',');
- var techniquesStr = [];
-
- for (var i = 0; i < techniques.length; i++) {
- techniques[i] = techniques[i].split('.');
- techniquesStr.push('<a href="http://www.w3.org/TR/WCAG20-TECHS/' + techniques[i][0] + '" target="_blank">' + techniques[i][0] + '</a>');
+
+ var standardObj = HTMLCS.util.getElementWindow(_doc)['HTMLCS_' + standard];
+ var msgInfo = [];
+ if (standardObj.getMsgInfo) {
+ msgInfo = standardObj.getMsgInfo(message.code);
}
var msgDiv = _doc.createElement('li');
@@ -789,16 +771,18 @@ var HTMLCSAuditor = new function()
msgTitle.className = _prefix + 'issue-title';
msgTitle.innerHTML = message.msg;
- var msgWcagRef = _doc.createElement('div');
- msgWcagRef.className = _prefix + 'issue-wcag-ref';
+ var msgRef = _doc.createElement('div');
+ msgRef.className = _prefix + 'issue-wcag-ref';
- var refContent = '<em>Principle:</em> <a href="' + principles[principle].link + '" target="_blank">' + principles[principle].name + '</a><br/>';
- refContent += '<em>Technique:</em> ' + techniquesStr.join(' '); + '<br/>';
- msgWcagRef.innerHTML = refContent;
+ var refContent = '';
+ for (var i = 0; i < msgInfo.length; i++) {
+ refContent += '<em>' + msgInfo[i][0] + ':</em> ' + msgInfo[i][1] + '<br/>';
+ }
+ msgRef.innerHTML = refContent;
msgDetailsDiv.appendChild(msgType);
msgDetailsDiv.appendChild(msgTitle);
- msgDetailsDiv.appendChild(msgWcagRef);
+ msgDetailsDiv.appendChild(msgRef);
msgDiv.appendChild(msgDetailsDiv);
// If the item cannot be pointed to, tell them why.
@@ -1302,13 +1286,28 @@ var HTMLCSAuditor = new function()
}
}
+ this.getStandardList = function() {
+ var pattern = /^HTMLCS_[^_]+$/;
+ var standards = [];
+ for (i in window) {
+ if (pattern.test(i) === true) {
+ var standard = window[i];
+ if (standard.sniffs && standard.name) {
+ standards.push(i.substr(7));
+ }
+ }
+ }
+
+ return standards;
+ };
+
/**
* Run HTML_CodeSniffer and place the results in the auditor.
*
* @returns undefined
*/
this.run = function(standard, source, options) {
- var standards = ['WCAG2AAA', 'WCAG2AA', 'WCAG2A'];
+ var standards = this.getStandardList();
var standardsToLoad = [];
for (var i = 0; i < standards.length; i++) {
if (!window['HTMLCS_' + standards[i]]) {
@@ -1921,16 +1920,20 @@ var HTMLCSAuditor = new function()
},
getPointer: function(targetElement) {
- var doc = targetElement.ownerDocument;
- HTMLCSAuditor.includeCss('HTMLCS', doc);
- var c = 'HTMLCS';
-
- var myPointer = doc.getElementById(c + '-pointer');
- if (!myPointer) {
- myPointer = doc.createElement('div');
- myPointer.id = c + '-pointer';
- myPointer.className = c + '-pointer ' + c + '-pointer-hidden';
- doc.body.appendChild(myPointer);
+ try {
+ var doc = targetElement.ownerDocument;
+ HTMLCSAuditor.includeCss('HTMLCS', doc);
+ var c = 'HTMLCS';
+
+ var myPointer = doc.getElementById(c + '-pointer');
+ if (!myPointer) {
+ myPointer = doc.createElement('div');
+ myPointer.id = c + '-pointer';
+ myPointer.className = c + '-pointer ' + c + '-pointer-hidden';
+ doc.body.appendChild(myPointer);
+ }
+ } catch (ex) {
+ // Can't get to owner document due to unsafe access.
}
return myPointer;
View
173 HTMLCS.js
@@ -1119,6 +1119,179 @@ var HTMLCS = new function()
return text;
};
+ /**
+ * Test for the correct headers attributes on table cell elements.
+ *
+ * Return value contains the following elements:
+ * - required (Boolean): Whether header association at all is required.
+ * - used (Boolean): Whether headers attribute has been used on at least
+ * one table data (td) cell.
+ * - allowScope (Boolean): Whether scope is allowed to satisfy the association
+ * requirement (ie. max one row/one column).
+ * - correct (Boolean): Whether headers have been correctly used.
+ * - missingThId (Array): Array of th elements without IDs.
+ * - missingTd (Array): Array of elements without headers attribute.
+ * - wrongHeaders (Array): Array of elements where headers attr is incorrect.
+ * Each is a structure with following keys: element,
+ * expected [headers attr], actual [headers attr].
+ *
+ * @param {DOMNode} element Table element to test upon.
+ *
+ * @return {Object} The above return value structure.
+ */
+ this.testTableHeaders = function(element)
+ {
+ var retval = {
+ required: true,
+ used: false,
+ correct: true,
+ allowScope: true,
+ missingThId: [],
+ missingTd: [],
+ wrongHeaders: []
+ }
+
+ var rows = element.getElementsByTagName('tr');
+ var tdCells = {};
+ var skipCells = [];
+
+ // Header IDs already used.
+ var headerIds = {
+ rows: [],
+ cols: []
+ };
+ var multiHeaders = {
+ rows: 0,
+ cols: 0
+ }
+ var missingIds = false;
+
+ for (var rownum = 0; rownum < rows.length; rownum++) {
+ var row = rows[rownum];
+ var colnum = 0;
+
+ for (var item = 0; item < row.childNodes.length; item++) {
+ var cell = row.childNodes[item];
+ if (cell.nodeType === 1) {
+ // Skip columns that are skipped due to rowspan.
+ if (skipCells[rownum]) {
+ while (skipCells[rownum][0] === colnum) {
+ skipCells[rownum].shift();
+ colnum++;
+ }
+ }
+
+ var nodeName = cell.nodeName.toLowerCase();
+ var rowspan = Number(cell.getAttribute('rowspan')) || 1;
+ var colspan = Number(cell.getAttribute('colspan')) || 1;
+
+ // If rowspanned, mark columns as skippable in the following
+ // row(s).
+ if (rowspan > 1) {
+ for (var i = rownum + 1; i < rownum + rowspan; i++) {
+ if (!skipCells[i]) {
+ skipCells[i] = [];
+ }
+
+ for (var j = colnum; j < colnum + colspan; j++) {
+ skipCells[i].push(j);
+ }
+ }
+ }
+
+ if (nodeName === 'th') {
+ var id = (cell.getAttribute('id') || '');
+
+ // Save the fact that we have a missing ID on the header.
+ if (id === '') {
+ retval.correct = false;
+ retval.missingThId.push(cell);
+ }
+
+ if ((rowspan > 1) && (colspan > 1)) {
+ // Multi-column AND multi-row header. Abandon all hope,
+ // As it must span across more than one row+column
+ retval.allowScope = false;
+ } else if (retval.allowScope === true) {
+ // If we haven't had a th in this column (row) yet,
+ // record it. if we find another th in this column (row),
+ // record that has multi-ths. If we already have a column
+ // (row) with multi-ths, we cannot use scope.
+ if (headerIds.cols[colnum] === undefined) {
+ headerIds.cols[colnum] = 0;
+ }
+
+ if (headerIds.rows[rownum] === undefined) {
+ headerIds.rows[rownum] = 0;
+ }
+
+ headerIds.rows[rownum] += colspan;
+ headerIds.cols[colnum] += rowspan;
+ }//end if
+ } else if ((nodeName === 'td')) {
+ if ((cell.hasAttribute('headers') === true) && (/^\s*$/.test(cell.getAttribute('headers')) === false)) {
+ retval.used = true;
+ }
+ }//end if
+
+ colnum += colspan;
+ }//end if
+ }//end for
+ }//end for
+
+ for (var i = 0; i < headerIds.rows.length; i++) {
+ if (headerIds.rows[i] > 1) {
+ multiHeaders.rows++;
+ }
+ }
+
+ for (var i = 0; i < headerIds.cols.length; i++) {
+ if (headerIds.cols[i] > 1) {
+ multiHeaders.cols++;
+ }
+ }
+
+ if ((multiHeaders.rows > 1) || (multiHeaders.cols > 1)) {
+ retval.allowScope = false;
+ } else if ((retval.allowScope === true) && ((multiHeaders.rows === 0) || (multiHeaders.cols === 0))) {
+ // If only one column OR one row header.
+ retval.required = false;
+ }//end if
+
+ // Calculate expected heading IDs. If they are not there or incorrect, flag
+ // them.
+ var cells = HTMLCS.util.getCellHeaders(element);
+ for (var i = 0; i < cells.length; i++) {
+ var cell = cells[i].cell;
+ var expected = cells[i].headers;
+
+ if (cell.hasAttribute('headers') === false) {
+ retval.correct = false;
+ retval.missingTd.push(cell);
+ } else {
+ var actual = (cell.getAttribute('headers') || '').split(/\s+/);
+ if (actual.length === 0) {
+ retval.correct = false;
+ retval.missingTd.push(cell);
+ } else {
+ actual = ' ' + actual.sort().join(' ') + ' ';
+ actual = actual.replace(/\s+/g, ' ').replace(/(\w+\s)\1+/g, '$1').replace(/^\s*(.*?)\s*$/g, '$1');
+ if (expected !== actual) {
+ retval.correct = false;
+ var val = {
+ element: cell,
+ expected: expected,
+ actual: (cell.getAttribute('headers') || '')
+ }
+ retval.wrongHeaders.push(val);
+ }
+ }//end if
+ }//end if
+ }//end for
+
+ return retval;
+ };
+
/**
* Return expected cell headers from a table.
*
Oops, something went wrong.

0 comments on commit 7a2678c

Please sign in to comment.