/** * +--------------------------------------------------------------------+ * | 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. | * +--------------------------------------------------------------------+ * */ var 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; /** * Loads the specifid standard and run the sniffs. * * @param {String} standard The name of the standard to load. * @param {String|Node} An HTML string or a DOM node object. * @param {Function} The function that will be called when the testing is completed. */ this.process = function(standard, content, callback) { // Clear previous runs. _standards = {}; _sniffs = []; _tags = {}; _standard = null; if (!content) { return false; } if (_standards[_getStandardPath(standard)]) { HTMLCS.run(callback, content); } else { this.loadStandard(standard, function() { HTMLCS.run(callback, content); }); } }; /** * 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) { if (!standard) { return false; } _includeStandard(standard, function() { _standard = standard; callback.call(this); }); }; /** * 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 = _getAllTags(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('' + content + ''); } 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 = _getAllTags(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 , * or + 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(' 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 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, 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 = window['HTMLCS_' + parts[(parts.length - 2)]]; if (ruleSet) { // Already included. _registerStandard(standard, callback, options); } else { _includeScript(standard, function() { // Script is included now register the standard. _registerStandard(standard, callback, options); }); }//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, options) { // Get the object name. var parts = standard.split('/'); // Get a copy of the ruleset object. var oldRuleSet = window['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); }; /** * 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) { if (sniffs.length === 0) { callback.call(this); return; } // Include and register sniffs. var sniff = sniffs.shift(); _loadSniffFile(standard, sniff, function() { _registerSniffs(standard, sniffs, callback); }); }; /** * 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) { 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); } } 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); }, { 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(); } if (watchedTags && watchedTags.length > 0) { 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