Current section

Files

Jump to
excessibility assets node_modules html_codesniffer HTMLCS.js
Raw

assets/node_modules/html_codesniffer/HTMLCS.js

/**
* +--------------------------------------------------------------------+
* | This HTML_CodeSniffer file is Copyright (c) |
* | Squiz Pty Ltd (ABN 77 084 670 600) |
* +--------------------------------------------------------------------+
* | IMPORTANT: Your use of this Software is subject to the terms of |
* | the Licence provided in the file licence.txt. If you cannot find |
* | this file please contact Squiz (www.squiz.com.au) so we may |
* | provide you a copy. |
* +--------------------------------------------------------------------+
*
*/
_global.HTMLCS = new function()
{
var _standards = {};
var _sniffs = [];
var _tags = {};
var _standard = null;
var _currentSniff = null;
var _messages = [];
var _msgOverrides = {};
/*
Message type constants.
*/
this.ERROR = 1;
this.WARNING = 2;
this.NOTICE = 3;
// The current language to use.
this.lang = 'en';
/**
* Loads the specified standard and run the sniffs.
*
* @param {String} standard The name of the standard to load.
* @param {String|Node} content An HTML string or a DOM node object.
* @param {Function} callback The function that will be called when the testing is completed.
* @param {Function} failCallback The fail callback which will be called if the standard load has failed.
* @param {String} language The language to use for text output.
*/
this.process = function(
standard,
content,
callback,
failCallback,
language
) {
// Clear previous runs.
_standards = {};
_sniffs = [];
_tags = {};
_standard = null;
if (!content) {
return false;
}
// Set a language to use.
var languages = Object.keys(_global.translation);
if (language && languages.indexOf(language) !== -1) {
this.lang = language;
}
if (_standards[_getStandardPath(standard)]) {
HTMLCS.run(callback, content);
} else {
this.loadStandard(standard, function() {
HTMLCS.run(callback, content);
}, failCallback);
}
};
/**
* Gets a translation for a text value.
*
* @param {String} text The text to get the translation for.
*
* @return {String}
*/
this.getTranslation = function(text) {
try {
return _global.translation[this.lang][text];
} catch (e) {
throw new Error('Translation for "' + text + '" does not exist in current language ' + this.lang);
}
};
/**
* Loads the specified standard and its sniffs.
*
* @param {String} standard The name of the standard to load.
* @param {Function} callback The function to call once the standard is loaded.
*/
this.loadStandard = function(standard, callback, failCallback) {
if (!standard) {
return false;
}
_includeStandard(standard, function() {
_standard = standard;
callback.call(this);
}, failCallback);
};
/**
* Runs the sniffs for the loaded standard.
*
* @param {Function} callback The function to call once all sniffs are completed.
* @param {String|Node} content An HTML string or a DOM node object.
*/
this.run = function(callback, content) {
var element = null;
var loadingFrame = false;
if (typeof content === 'string') {
loadingFrame = true;
var elementFrame = document.createElement('iframe');
elementFrame.style.display = 'none';
elementFrame = document.body.insertBefore(elementFrame, null);
if (elementFrame.contentDocument) {
element = elementFrame.contentDocument;
} else if (element.contentWindow) {
element = elementFrame.contentWindow.document;
}
elementFrame.load = function() {
this.onreadystatechange = null;
this.onload = null;
if (HTMLCS.isFullDoc(content) === false) {
element = element.getElementsByTagName('body')[0];
var div = element.getElementsByTagName('div')[0];
if (div && (div.id === '__HTMLCS-source-wrap')) {
div.id = '';
element = div;
}
}
var elements = HTMLCS.util.getAllElements(element);
elements.unshift(element);
_run(elements, element, callback);
};
// Satisfy IE which doesn't like onload being set dynamically.
elementFrame.onreadystatechange = function() {
if (/^(complete|loaded)$/.test(this.readyState) === true) {
this.onreadystatechange = null;
this.load();
}
};
elementFrame.onload = elementFrame.load;
if ((HTMLCS.isFullDoc(content) === false) && (content.indexOf('<body') === -1)) {
element.write('<div id="__HTMLCS-source-wrap">' + content + '</div>');
} else {
element.write(content);
}
element.close();
} else {
element = content;
}
if (!element) {
callback.call(this);
return;
}
callback = callback || function() {};
_messages = [];
// Get all the elements in the parent element.
// Add the parent element too, which will trigger "_top" element codes.
var elements = HTMLCS.util.getAllElements(element);
elements.unshift(element);
// Run the sniffs.
if (loadingFrame === false) {
_run(elements, element, callback);
}
};
/**
* Returns true if the content passed appears to be from a full document.
*
* With string content, we consider a full document as the presence of <html>,
* or <head> + <body> elements. For an element, only the 'html' element (the
* document element) is accepted.
*
* @param {String|Node} content An HTML string or a DOM node object.
*
* @returns {Boolean}
*/
this.isFullDoc = function(content) {
var fullDoc = false;
if (typeof content === 'string') {
if (content.toLowerCase().indexOf('<html') !== -1) {
fullDoc = true;
} else if ((content.toLowerCase().indexOf('<head') !== -1) && (content.toLowerCase().indexOf('<body') !== -1)) {
fullDoc = true;
}
} else {
// If we are the document, or the document element.
if ((content.nodeName.toLowerCase() === 'html') || (content.documentElement)) {
fullDoc = true;
}
}
return fullDoc;
};
/**
* Adds a message.
*
* @param {Number} type The type of the message.
* @param {Node} element The element that the message is related to.
* @param {String} msg The message string.
* @param {String} code Unique code for the message.
* @param {Object} [data] Extra data to store for the message.
*/
this.addMessage = function(type, element, msg, code, data) {
code = _getMessageCode(code);
_messages.push({
type: type,
element: element,
msg: _msgOverrides[code] || msg,
code: code,
data: data
});
};
/**
* Returns all the messages for the last run.
*
* Return a copy of the array so the class variable doesn't get modified by
* future modification (eg. splicing).
*
* @returns {Array} Array of message objects.
*/
this.getMessages = function() {
return _messages.concat([]);
};
/**
* Runs the sniffs in the loaded standard for the specified element.
*
* @param {Node} element The element to test.
* @param {Node} topElement The top element of the processing.
* @param {Function} [callback] The function to call once all tests are run.
*/
var _run = function(elements, topElement, callback) {
var topMsgs = [];
while (elements.length > 0) {
var element = elements.shift();
if (element === topElement) {
var tagName = '_top';
} else {
var tagName = element.tagName.toLowerCase();
}
// First check whether any "top" messages need to be shifted off for this
// element. If so, dump off into the main messages.
for (var i = 0; i < topMsgs.length;) {
if (element === topMsgs[i].element) {
_messages.push(topMsgs[i]);
topMsgs.splice(i, 1);
} else {
i++;
}
}//end for
if (_tags[tagName] && _tags[tagName].length > 0) {
_processSniffs(element, _tags[tagName].concat([]), topElement);
// Save "top" messages, and reset the messages array.
if (tagName === '_top') {
topMsgs = _messages;
_messages = [];
}
}
}//end while
_messages = _messages.concat(topMsgs);
// Due to filtering of presentation roles for general sniffing these need to be handled
// separately. The 1.3.1 sniff needs to run to detect any incorrect usage of the presentation
// role.
var presentationElems = topElement.querySelectorAll('[role="presentation"]');
_currentSniff = HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1;
[].forEach.call(presentationElems, function(element) {
_currentSniff.testSemanticPresentationRole(element);
});
if (callback instanceof Function === true) {
callback.call(this);
}
};
/**
* Process the sniffs.
*
* @param {Node} element The element to test.
* @param {Array} sniffs Array of sniffs.
* @param {Node} topElement The top element of the processing.
* @param {Function} [callback] The function to call once the processing is completed.
*/
var _processSniffs = function(element, sniffs, topElement, callback) {
while (sniffs.length > 0) {
var sniff = sniffs.shift();
_currentSniff = sniff;
if (sniff.useCallback === true) {
// If the useCallback property is set:
// - Process the sniff.
// - Recurse into ourselves with remaining sniffs, with no callback.
// - Clear out the list of sniffs (so they aren't run again), so the
// callback (if not already recursed) can run afterwards.
sniff.process(element, topElement, function() {
_processSniffs(element, sniffs, topElement);
sniffs = [];
});
} else {
// Process the sniff.
sniff.process(element, topElement);
}
}//end while
if (callback instanceof Function === true) {
callback.call(this);
}
};
/**
* Includes the specified standard file.
*
* @param {String} standard The name of the standard.
* @param {Function} callback The function to call once the standard is included.
* @param {Object} options The options for the standard (e.g. exclude sniffs).
*/
var _includeStandard = function(standard, callback, failCallback, options) {
if (standard.indexOf('http') !== 0) {
standard = _getStandardPath(standard);
}//end id
// See if the ruleset object is already included (eg. if minified).
var parts = standard.split('/');
var ruleSet = _global['HTMLCS_' + parts[(parts.length - 2)]];
if (ruleSet) {
// Already included.
_registerStandard(standard, callback, failCallback, options);
} else {
_includeScript(standard, function() {
// Script is included now register the standard.
_registerStandard(standard, callback, failCallback, options);
}, failCallback);
}//end if
};
/**
* Registers the specified standard and its sniffs.
*
* @param {String} standard The name of the standard.
* @param {Function} callback The function to call once the standard is registered.
* @param {Object} options The options for the standard (e.g. exclude sniffs).
*/
var _registerStandard = function(standard, callback, failCallback, options) {
// Get the object name.
var parts = standard.split('/');
// Get a copy of the ruleset object.
var oldRuleSet = _global['HTMLCS_' + parts[(parts.length - 2)]];
var ruleSet = {};
for (var x in oldRuleSet) {
if (oldRuleSet.hasOwnProperty(x) === true) {
ruleSet[x] = oldRuleSet[x];
}
}
if (!ruleSet) {
return false;
}
_standards[standard] = ruleSet;
// Process the options.
if (options) {
if (options.include && options.include.length > 0) {
// Included sniffs.
ruleSet.sniffs = options.include;
} else if (options.exclude) {
// Excluded sniffs.
for (var i = 0; i < options.exclude.length; i++) {
var index = ruleSet.sniffs.find(options.exclude[i]);
if (index >= 0) {
ruleSet.sniffs.splice(index, 1);
}
}
}
}//end if
// Register the sniffs for this standard.
var sniffs = ruleSet.sniffs.slice(0, ruleSet.sniffs.length);
_registerSniffs(standard, sniffs, callback, failCallback);
};
/**
* Registers the sniffs for the specified standard.
*
* @param {String} standard The name of the standard.
* @param {Array} sniffs List of sniffs to register.
* @param {Function} callback The function to call once the sniffs are registered.
*/
var _registerSniffs = function(standard, sniffs, callback, failCallback) {
if (sniffs.length === 0) {
callback.call(this);
return;
}
// Include and register sniffs.
var sniff = sniffs.shift();
_loadSniffFile(standard, sniff, function() {
_registerSniffs(standard, sniffs, callback, failCallback);
}, failCallback);
};
/**
* Includes the sniff's JS file and registers it.
*
* @param {String} standard The name of the standard.
* @param {String|Object} sniff The sniff to register, can be a string or
* and object specifying another standard.
* @param {Function} callback The function to call once the sniff is included and registered.
*/
var _loadSniffFile = function(standard, sniff, callback, failCallback) {
if (typeof sniff === 'string') {
var sniffObj = _getSniff(standard, sniff);
var cb = function() {
_registerSniff(standard, sniff);
callback.call(this);
};
// Already loaded.
if (sniffObj) {
cb();
} else {
_includeScript(_getSniffPath(standard, sniff), cb, failCallback);
}
} else {
// Including a whole other standard.
_includeStandard(sniff.standard, function() {
if (sniff.messages) {
// Add message overrides.
for (var msg in sniff.messages) {
_msgOverrides[msg] = sniff.messages[msg];
}
}
callback.call(this);
}, failCallback, {
exclude: sniff.exclude,
include: sniff.include
});
}
};
/**
* Registers the specified sniff.
*
* @param {String} standard The name of the standard.
* @param {String} sniff The name of the sniff.
*/
var _registerSniff = function(standard, sniff) {
// Get the sniff object.
var sniffObj = _getSniff(standard, sniff);
if (!sniffObj) {
return false;
}
// Call the register method of the sniff, it should return an array of tags.
if (sniffObj.register) {
var watchedTags = sniffObj.register();
for (var i = 0; i < watchedTags.length; i++) {
if (!_tags[watchedTags[i]]) {
_tags[watchedTags[i]] = [];
}
_tags[watchedTags[i]].push(sniffObj);
}
}
_sniffs.push(sniffObj);
};
/**
* Returns the path to the sniff file.
*
* @param {String} standard The name of the standard.
* @param {String} sniff The name of the sniff.
*
* @returns {String} The path to the JS file of the sniff.
*/
var _getSniffPath = function(standard, sniff) {
var parts = standard.split('/');
parts.pop();
var path = parts.join('/') + '/Sniffs/' + sniff.replace(/\./g, '/') + '.js';
return path;
};
/**
* Returns the path to a local standard.
*
* @param {String} standard The name of the standard.
*
* @returns {String} The path to the local standard.
*/
var _getStandardPath = function(standard)
{
// Get the include path of a local standard.
var scripts = document.getElementsByTagName('script');
var path = null;
// Loop through all the script tags that exist in the document and find the one
// that has included this file.
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src) {
if (scripts[i].src.match(/HTMLCS\.js/)) {
// We have found our appropriate <script> tag that includes
// this file, we can extract the path.
path = scripts[i].src.replace(/HTMLCS\.js/,'');
// trim any trailing bits
path = path.substring(0, path.indexOf('?'));
break;
}
}
}
return path + 'Standards/' + standard + '/ruleset.js';
};
/**
* Returns the sniff object.
*
* @param {String} standard The name of the standard.
* @param {String} sniff The name of the sniff.
*
* @returns {Object} The sniff object.
*/
var _getSniff = function(standard, sniff) {
var name = 'HTMLCS_';
name += _standards[standard].name + '_Sniffs_';
name += sniff.split('.').join('_');
if (!_global[name]) {
return null;
}
_global[name]._name = sniff;
return _global[name];
};
/**
* Returns the full message code.
*
* A full message code includes the standard name, the sniff name and the given code.
*
* @returns {String} The full message code.
*/
var _getMessageCode = function(code) {
code = _standard + '.' + _currentSniff._name + '.' + code;
return code;
};
/**
* Includes the specified JS file.
*
* @param {String} src The URL to the JS file.
* @param {Function} callback The function to call once the script is loaded.
*/
var _includeScript = function(src, callback, failCallback) {
var script = document.createElement('script');
script.onload = function() {
script.onload = null;
script.onreadystatechange = null;
callback.call(this);
};
script.onerror = function() {
script.onload = null;
script.onreadystatechange = null;
if (failCallback) {
failCallback.call(this);
}
};
script.onreadystatechange = function() {
if (/^(complete|loaded)$/.test(this.readyState) === true) {
script.onreadystatechange = null;
script.onload();
}
};
script.src = src;
if (document.head) {
document.head.appendChild(script);
} else {
document.getElementsByTagName('head')[0].appendChild(script);
}
};
};