├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── package.json ├── tasks ├── html_validation.js └── lib │ └── remoteval.js └── test ├── html ├── 404.html ├── about.html ├── home.html └── index.html └── html_validation_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /_tempvlidation.html 3 | /node_modules/ 4 | /npm-debug.log 5 | /validation-files.json 6 | /validation-report.json 7 | /validation-status.json 8 | tmp 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "indent": 4, 6 | "latedef": true, 7 | "newcap": true, 8 | "noarg": true, 9 | "node": true, 10 | "quotmark": "single", 11 | "undef": true, 12 | "unused": true 13 | } 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | before_install: 6 | - npm update npm -g 7 | - npm install -g grunt-cli 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-html-validation 3 | * https://github.com/praveen/grunt-html-validation 4 | * 5 | * Copyright (c) 2013 - 2014 praveenvijayan 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(grunt) { 12 | 13 | // Project configuration. 14 | grunt.initConfig({ 15 | jshint: { 16 | all: [ 17 | 'Gruntfile.js', 18 | 'tasks/**/*.js', 19 | '<%= nodeunit.tests %>' 20 | ], 21 | options: { 22 | jshintrc: '.jshintrc' 23 | } 24 | }, 25 | 26 | // Before generating any new files, remove any previously-created files. 27 | clean: { 28 | tests: ['tmp'] 29 | }, 30 | 31 | // Unit tests. 32 | nodeunit: { 33 | tests: ['test/*_test.js'] 34 | }, 35 | validation: { // Grunt w3c validation plugin 36 | options: { 37 | reset: grunt.option('reset') || false, 38 | stoponerror:false, 39 | // remotePath: "http://decodize.com/", 40 | // remoteFiles: ["html/slidemote-universal-remote-control-for-html5-presentations", 41 | // "GAE/linktomob-share-your-links-quickly-and-easily-on-mobile-devices/", 42 | // "html/getting-started-with-yeoman-1-dot-0-beta-on-windows/", 43 | // "html/moving-from-wordpress-to-octopress/", 44 | // "css/site-preloading-methods/", 45 | // "html/sublime-text-2-bidirectional-language-support-plugin/"] 46 | // remoteFiles: "validation-files.json", 47 | relaxerror: ['Bad value X-UA-Compatible for attribute http-equiv on element meta.', 48 | 'Element title must not be empty.'] 49 | }, 50 | files: { 51 | src: ['test/html/*.html', 52 | '!test/html/index.html', 53 | '!test/html/404.html'] 54 | } 55 | } 56 | 57 | }); 58 | 59 | // Actually load this plugin's task(s). 60 | grunt.loadTasks('tasks'); 61 | 62 | // These plugins provide necessary tasks. 63 | grunt.loadNpmTasks('grunt-contrib-jshint'); 64 | grunt.loadNpmTasks('grunt-contrib-clean'); 65 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 66 | // grunt.loadNpmTasks('grunt-html-validation'); 67 | 68 | 69 | // Whenever the 'test' task is run, first clean the 'tmp' dir, then run this 70 | // plugin's task(s), then test the result. 71 | grunt.registerTask('test', ['clean', 'jshint', 'nodeunit']); 72 | 73 | grunt.registerTask('multiple', ['validation', 'clean', 'nodeunit']); 74 | 75 | // By default, lint and run all tests. 76 | grunt.registerTask('default', ['test']); 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 - 2014 praveenvijayan 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-html-validation [![Build Status](https://travis-ci.org/praveenvijayan/grunt-html-validation.png?branch=master)](https://travis-ci.org/praveenvijayan/grunt-html-validation) 2 | 3 | [![NPM](https://nodei.co/npm/grunt-html-validation.png?downloads=true)](https://nodei.co/npm/grunt-html-validation/) 4 | 5 | > W3C html validation grunt plugin. Validate all files in a directory automatically. 6 | 7 | ## Getting Started 8 | This plugin requires Grunt `~0.4.1` 9 | 10 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 11 | 12 | ```shell 13 | npm install grunt-html-validation --save-dev 14 | ``` 15 | 16 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 17 | 18 | ```js 19 | grunt.loadNpmTasks('grunt-html-validation'); 20 | ``` 21 | 22 | And add to your task list using `validation`: 23 | 24 | ```js 25 | grunt.registerTask('default', ['validation']); 26 | ``` 27 | 28 | ## The "validation" task 29 | 30 | ### Overview 31 | In your project's Gruntfile, add a section named `validation` to the data object passed into `grunt.initConfig()`. 32 | 33 | ```js 34 | validation: { 35 | options: { 36 | reset: grunt.option('reset') || false, 37 | stoponerror: false, 38 | remotePath: 'http://decodize.com/', 39 | remoteFiles: ['html/moving-from-wordpress-to-octopress/', 40 | 'css/site-preloading-methods/'], //or 41 | remoteFiles: 'validation-files.json', // JSON file contains array of page paths. 42 | relaxerror: ['Bad value X-UA-Compatible for attribute http-equiv on element meta.'] //ignores these errors 43 | }, 44 | files: { 45 | src: ['<%= yeoman.app %>/*.html', 46 | '!<%= yeoman.app %>/index.html', 47 | '!<%= yeoman.app %>/modules.html', 48 | '!<%= yeoman.app %>/404.html'] 49 | } 50 | } 51 | ``` 52 | 53 | ### Options 54 | 55 | #### options.reset 56 | Type: `Boolean`
57 | Default value: `'false'` 58 | 59 | Resets all the validated files status. When want to revalidate all the validated files - 60 | `eg: sudo grunt validate --reset=true` 61 | 62 | #### options.proxy 63 | Type: `String`
64 | Default value: `null` 65 | 66 | Setup your proxy when you are behind a corporate proxy and encounters `ETIMEDOUT`. 67 | 68 | ```js 69 | proxy: 'http://proxy:8080' 70 | ``` 71 | 72 | #### options.serverUrl 73 | Type: `String`
74 | Default value: `null` 75 | 76 | Supply a different validator server URL, for instance [if you run a local server](http://validator.w3.org/source/). 77 | Eg: `http://localhost/w3c-validator/check` 78 | 79 | #### options.path 80 | Type: `String`
81 | Default value: `'validation-status.json'` 82 | 83 | Default file for storing validation information. 84 | 85 | #### options.reportpath 86 | Type: `String`
87 | Default value: `validation-report.json` 88 | 89 | Consolidated report in JSON format, if reportpath is `false` it will not generated. 90 | 91 | #### options.stoponerror 92 | Type: `Boolean`
93 | Default value: `false` 94 | 95 | When hit by a validation error, html-validator continue validating next file by default and this process continues until all files in the list completes validation. If 'stoponerror' set to `true`, validator will stop validating next file. 96 | 97 | #### options.maxTry 98 | Type: `Number`
99 | Default value: `3` 100 | 101 | Number of retries when network error occuers. Default case, after 3 reties validator will move to next file. 102 | 103 | #### options.remotePath 104 | Type: `String`
105 | Default value: `` 106 | 107 | #### options.wrapfile 108 | Type: `String`
109 | Default value: `` 110 | 111 | File that will wrap your files inside. 112 | 113 | The file must contain a comment that will be replaced by each file content: **<!-- CONTENT -->** 114 | 115 | Useful to validate partials because w3c validator need <html>, <head>, <body>... 116 | 117 | Note: line reported will be the partial line, if you see a negative number this means that the error is in the *wrapfile*. 118 | 119 | example 120 | 121 | ```html 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | ``` 131 | 132 | 133 | 134 | #### options.remoteFiles 135 | Type: `Array`
136 | Default value: `` 137 | 138 | Array of page paths to be validated. When remote files are not present validator will append file names from local folder. `remotePath` is mandatory when this option is specified. 139 | 140 | eg: 141 | 142 | ```js 143 | remoteFiles: ['html/moving-from-wordpress-to-octopress/', 144 | 'css/site-preloading-methods/'] 145 | ``` 146 | 147 | you can also provide a file that contains an array of pages. 148 | 149 | ```js 150 | remoteFiles: 'validation-files.json' 151 | ``` 152 | 153 | ```js 154 | ['html/getting-started-with-yeoman-1-dot-0-beta-on-windows', 155 | 'html/slidemote-universal-remote-control-for-html5-presentations/', 156 | 'html/simple-responsive-image-technique/'] 157 | ``` 158 | 159 | #### options.relaxerror 160 | Type: `Array`
161 | Default value: `` 162 | 163 | Helps to skip certain w3c errors messages from validation. Give exact error message or a regular expression in an array & validator will ignore those relaxed errors from validation. 164 | 165 | ```js 166 | relaxerror: ['Bad value X-UA-Compatible for attribute http-equiv on element meta.', 167 | 'document type does not allow element "[A-Z]+" here'] 168 | ``` 169 | 170 | #### options.doctype 171 | Type: `String`
172 | Default value: `false` 173 | 174 | Set `false` for autodetect or chose one of this options: 175 | 176 | - `HTML5` 177 | - `XHTML 1.0 Strict` 178 | - `XHTML 1.0 Transitional` 179 | - `XHTML 1.0 Frameset` 180 | - `HTML 4.01 Strict` 181 | - `HTML 4.01 Transitional` 182 | - `HTML 4.01 Frameset` 183 | - `HTML 4.01 + RDFa 1.1` 184 | - `HTML 3.2` 185 | - `HTML 2.0` 186 | - `ISO/IEC 15445:2000 ("ISO HTML")` 187 | - `XHTML 1.1` 188 | - `XHTML + RDFa` 189 | - `XHTML Basic 1.0` 190 | - `XHTML Basic 1.1` 191 | - `XHTML Mobile Profile 1.2` 192 | - `XHTML-Print 1.0` 193 | - `XHTML 1.1 plus MathML 2.0` 194 | - `XHTML 1.1 plus MathML 2.0 plus SVG 1.1` 195 | - `MathML 2.0` 196 | - `SVG 1.0` 197 | - `SVG 1.1` 198 | - `SVG 1.1 Tiny` 199 | - `SVG 1.1 Basic` 200 | - `SMIL 1.0` 201 | - `SMIL 2.0` 202 | 203 | 204 | #### options.charset 205 | Type: `String`
206 | Default value: `false` 207 | 208 | Set `false` for autodetect or chose one of this options: 209 | 210 | - `utf-8` 211 | - `utf-16` 212 | - `iso-8859-1` 213 | - `iso-8859-2` 214 | - `iso-8859-3` 215 | - `iso-8859-4` 216 | - `iso-8859-5` 217 | - `iso-8859-6-i` 218 | - `iso-8859-7` 219 | - `iso-8859-8` 220 | - `iso-8859-8-i` 221 | - `iso-8859-9` 222 | - `iso-8859-10` 223 | - `iso-8859-11` 224 | - `iso-8859-13` 225 | - `iso-8859-14` 226 | - `iso-8859-15` 227 | - `iso-8859-16` 228 | - `us-ascii` 229 | - `euc-jp` 230 | - `shift_jis` 231 | - `iso-2022-jp` 232 | - `euc-kr` 233 | - `gb2312` 234 | - `gb18030` 235 | - `big5` 236 | - `big5-HKSCS` 237 | - `tis-620` 238 | - `koi8-r` 239 | - `koi8-u` 240 | - `iso-ir-111` 241 | - `macintosh` 242 | - `windows-1250` 243 | - `windows-1251` 244 | - `windows-1252` 245 | - `windows-1253` 246 | - `windows-1254` 247 | - `windows-1255` 248 | - `windows-1256` 249 | - `windows-1257` 250 | 251 | #### options.failHard 252 | Type: `boolean`
253 | Default value: `false` 254 | 255 | If true, the task will fail at the end of its run if there were any validation errors that were not ignored via `options.relaxerror`. 256 | 257 | ## Contributing 258 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). 259 | 260 | Report issues [here](https://github.com/praveenvijayan/grunt-html-validation/issues) 261 | 262 | ## Release History 263 | * 2014-05-27 v0.1.18 Version bump, Fixes #54 264 | * 2014-05-15 v0.1.17 Fixes #50, #52 265 | * 2014-04-23 v0.1.16 Fixes 266 | * 2014-04-03 v0.1.15 Updated dependencies (jshnt, nodeunit, request), gitignore, code cleanup etc.. 267 | * 2014-03-23 v0.1.14 Updated with wrapfile & server url options. 268 | * 2013-12-26 v0.1.13 Fixed running multiple tasks fail due to validation failure. 269 | * 2013-12-17 v0.1.11 Option to set proxy, w3cjs updated to 0.1.22, added fail hard and some bug fixes 270 | * 2013-11-22 v0.1.9 Fix some bugs 271 | * 2013-11-22 v0.1.8 Added options for specify doctype and charset 272 | * 2013-11-22 v0.1.7 Added support for RegExp in relaxed validation 273 | * 2013-08-31   v0.1.6   Added relaxed validation, w3cjs updated from 0.1.9 to 0.1.10. 274 | * 2013-08-31   v0.1.5   Added remote validation support. Max network error retry count. 275 | * 2013-08-19   v0.1.4   Fixed issues. Added 'stoponerror' option, validation report added. 276 | * 2013-08-05   v0.1.2   Fixed issues. 277 | * 2013-04-20   v0.1.0   Initial release. 278 | 279 | 280 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/praveenvijayan/grunt-html-validation/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 281 | 282 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-html-validation", 3 | "description": "W3C html validation grunt plugin", 4 | "version": "0.1.18", 5 | "homepage": "https://github.com/praveenvijayan/grunt-html-validation", 6 | "author": "praveenvijayan (http://decodize.com)", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/praveenvijayan/grunt-html-validation.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/praveenvijayan/grunt-html-validation/issues" 13 | }, 14 | "licenses": [ 15 | { 16 | "type": "MIT", 17 | "url": "https://github.com/praveenvijayan/grunt-html-validation/blob/master/LICENSE-MIT" 18 | } 19 | ], 20 | "engines": { 21 | "node": ">= 0.8.0" 22 | }, 23 | "scripts": { 24 | "test": "grunt test" 25 | }, 26 | "peerDependencies": { 27 | "grunt": "~0.4.1" 28 | }, 29 | "dependencies": { 30 | "chalk": "^0.5.1", 31 | "colors": "~0.6.2", 32 | "request": "~2.40.0", 33 | "w3cjs": "~0.1.25" 34 | }, 35 | "devDependencies": { 36 | "grunt": "~0.4.5", 37 | "grunt-contrib-clean": "~0.6.0", 38 | "grunt-contrib-jshint": "~0.10.0", 39 | "grunt-contrib-nodeunit": "~0.4.1" 40 | }, 41 | "keywords": [ 42 | "gruntplugin" 43 | ], 44 | "files": [ 45 | "tasks", 46 | "LICENSE-MIT" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /tasks/html_validation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-html-validation 3 | * https://github.com/praveenvijayan/grunt-html-validation 4 | * 5 | * Copyright (c) 2013 - 2014 Praveen Vijayan 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function (grunt) { 12 | 13 | var w3cjs = require('w3cjs'); 14 | var colors = require('colors'); 15 | var chalk = require('chalk'); 16 | var rval = require('./lib/remoteval'); 17 | 18 | colors.setTheme({ 19 | silly: 'rainbow', 20 | input: 'grey', 21 | verbose: 'cyan', 22 | prompt: 'grey', 23 | info: 'green', 24 | data: 'grey', 25 | help: 'cyan', 26 | warn: 'yellow', 27 | debug: 'blue', 28 | error: 'red', 29 | blue: 'blue' 30 | }); 31 | 32 | var counter = 0, 33 | msg = { 34 | error: 'Something went wrong', 35 | ok: 'Validation successful..', 36 | start: 'Validation started for.. '.info, 37 | networkError: 'Network error re-validating..'.error, 38 | validFile: 'Validated skipping..', 39 | nofile: ':- No file is specified in the path!', 40 | nextfile: 'Skipping to next file..'.verbose, 41 | eof: 'End of File..'.verbose, 42 | fileNotFound: 'File not found..'.error, 43 | remotePathError: 'Remote path '.error + '(options->remotePath) '.grey + 44 | 'is mandatory when remote files '.error + 45 | '(options-> remoteFiles) '.grey + 'are specified!'.error 46 | }, 47 | len, 48 | reportArry = [], 49 | retryCount = 0, 50 | reportFilename = ''; 51 | 52 | grunt.registerMultiTask('validation', 'HTML W3C validation.', function () { 53 | // Merge task-specific and/or target-specific options with these defaults. 54 | var options = this.options({ 55 | path: 'validation-status.json', 56 | reportpath: 'validation-report.json', 57 | reset: false, 58 | proxy: null, 59 | stoponerror: false, 60 | failHard: false, 61 | remotePath: false, 62 | maxTry: 3, 63 | relaxerror: [], 64 | doctype: false, // Defaults false for autodetect 65 | charset: false // Defaults false for autodetect 66 | }); 67 | 68 | var done = this.async(), 69 | files = grunt.file.expand(this.filesSrc), 70 | flen = files.length, 71 | readSettings = {}, 72 | isRelaxError = false; 73 | 74 | isRelaxError = options.relaxerror.length && options.relaxerror.length !== ''; 75 | 76 | var makeFileList = function (files) { 77 | return files.map(function (file) { 78 | return options.remotePath + file; 79 | }); 80 | }; 81 | 82 | //Reset current validation status and start from scratch. 83 | if (options.reset) { 84 | grunt.file.write(options.path, '{}'); 85 | } 86 | 87 | if (!flen) { 88 | var nomsg = this.data.src; 89 | console.log(nomsg + msg.nofile.error); 90 | } 91 | 92 | var addToReport = function (fname, status) { 93 | var relaxedReport = []; 94 | 95 | for (var i = 0; i < status.length; i++) { 96 | if (!checkRelaxError(status[i].message)) { 97 | relaxedReport.push(status[i]); 98 | } 99 | } 100 | 101 | var report = {}; 102 | report.filename = fname; 103 | report.error = relaxedReport; 104 | reportArry.push(report); 105 | }; 106 | 107 | var wrapfile, 108 | wrapfile_line_start = 0; 109 | var validate = function (files) { 110 | if (files.length) { 111 | // fix: Fatal error: Unable to read 'undefined' file (Error code: ENOENT). 112 | if (!files[counter]) { 113 | done(); 114 | return; 115 | } 116 | 117 | 118 | if (grunt.file.exists(options.path)) { 119 | readSettings = grunt.file.readJSON(options.path); 120 | } 121 | var currFileStat = readSettings[files[counter]] || false; 122 | 123 | if (currFileStat) { 124 | console.log(msg.validFile.green + files[counter]); 125 | reportFilename = options.remoteFiles ? dummyFile[counter] : files[counter]; 126 | addToReport(reportFilename, false); 127 | counter++; 128 | validate(files); 129 | done(); 130 | return; 131 | } 132 | 133 | if (files[counter] !== undefined) { 134 | 135 | var filename = options.remoteFiles ? dummyFile[counter] : files[counter]; 136 | 137 | console.log(msg.start + filename); 138 | } 139 | 140 | var w3cjs_options = { 141 | //file: files[counter], // file can either be a local file or a remote file 142 | // file: 'http://localhost:9001/010_gul006_business_landing_o2_v11.html', 143 | output: 'json', // Defaults to 'json', other option includes html 144 | doctype: options.doctype, // Defaults false for autodetect 145 | charset: options.charset, // Defaults false for autodetect 146 | proxy: options.proxy, // Proxy to pass to the w3c library 147 | callback: function (res) { 148 | 149 | flen = files.length; 150 | 151 | if (!res.messages) { 152 | ++retryCount; 153 | var netErrorMsg = msg.networkError + ' ' + retryCount.toString().error + ' '; 154 | if (retryCount === options.maxTry) { 155 | counter++; 156 | if (counter !== flen) { 157 | netErrorMsg += msg.nextfile; 158 | } else { 159 | netErrorMsg += msg.eof; 160 | } 161 | retryCount = 0; 162 | } 163 | 164 | console.log(netErrorMsg); 165 | validate(files); 166 | return; 167 | } 168 | 169 | len = res.messages.length; 170 | 171 | var setGreen = function () { 172 | readSettings[files[counter]] = true; 173 | grunt.log.ok(msg.ok.green); 174 | 175 | reportFilename = options.remoteFiles ? dummyFile[counter] : files[counter]; 176 | addToReport(reportFilename, false); 177 | }; 178 | 179 | if (len) { 180 | var errorCount = 0, 181 | prop; 182 | 183 | for (prop in res.messages) { 184 | res.messages[prop].unwrapLine = res.messages[prop].lastLine - wrapfile_line_start; 185 | } 186 | 187 | for (prop in res.messages) { 188 | var chkRelaxError; 189 | if (isRelaxError) { 190 | chkRelaxError = checkRelaxError(res.messages[prop].message); 191 | } 192 | 193 | if (!chkRelaxError) { 194 | errorCount = errorCount + 1; 195 | 196 | var lineNumber = ' Line no: ' + JSON.stringify(options.wrapfile ? res.messages[prop].unwrapLine : res.messages[prop].lastLine); 197 | if (typeof(prompt) !== 'undefined') { 198 | lineNumber = lineNumber.prompt; 199 | } 200 | 201 | console.log(errorCount + '=> '.warn + JSON.stringify(res.messages[prop].message).help + lineNumber ); 202 | } 203 | 204 | } 205 | 206 | if (errorCount !== 0) { 207 | console.log('No of errors: '.error + errorCount); 208 | } 209 | 210 | readSettings[files[counter]] = false; 211 | reportFilename = options.remoteFiles ? dummyFile[counter] : files[counter]; 212 | addToReport(reportFilename, res.messages); 213 | 214 | if (options.stoponerror) { 215 | done(); 216 | return; 217 | } 218 | 219 | if (isRelaxError && errorCount === 0) { 220 | setGreen(); 221 | } 222 | 223 | } else { 224 | 225 | setGreen(); 226 | 227 | } 228 | 229 | grunt.file.write(options.path, JSON.stringify(readSettings)); 230 | // depending on the output type, res will either be a json object or a html string 231 | counter++; 232 | 233 | if (counter === flen) { 234 | if (options.reportpath) { 235 | grunt.file.write(options.reportpath, JSON.stringify(reportArry)); 236 | console.log('Validation report generated: '.green + options.reportpath); 237 | } 238 | if (options.failHard) { 239 | var validationErrCount = reportArry.reduce(function (sum, report) { 240 | return sum + report.error.length; 241 | }, 0); 242 | if (validationErrCount > 0) { 243 | grunt.fail.warn(validationErrCount + ' total unignored HTML validation error' + grunt.util.pluralize(validationErrCount, '/s') + '.'); 244 | } 245 | } 246 | done(); 247 | if (!options.remoteFiles) { 248 | return; 249 | } 250 | } 251 | 252 | if (options.remoteFiles) { 253 | if (counter === flen) { 254 | return; 255 | } 256 | 257 | rval(dummyFile[counter], function () { 258 | validate(files); 259 | }); 260 | 261 | } else { 262 | validate(files); 263 | } 264 | } 265 | }; 266 | 267 | if (options.wrapfile) { 268 | if (!wrapfile) { 269 | wrapfile = grunt.file.read(options.wrapfile); 270 | wrapfile_line_start = wrapfile.substring(0, wrapfile.indexOf('')).split('\n').length - 1; 271 | } 272 | 273 | w3cjs_options.input = wrapfile.replace('', grunt.file.read(files[counter])); 274 | } else { 275 | w3cjs_options.file = files[counter]; 276 | } 277 | 278 | // override default server 279 | if (options.serverUrl) { 280 | w3cjs.setW3cCheckUrl(options.serverUrl); 281 | } 282 | 283 | w3cjs.validate(w3cjs_options); 284 | } 285 | }; 286 | 287 | function checkRelaxError(error) { 288 | for (var i = 0, l = options.relaxerror.length; i < l; i++) { 289 | var re = new RegExp(options.relaxerror[i], 'g'); 290 | if (re.test(error)) { 291 | return true; 292 | } 293 | } 294 | } 295 | 296 | /* Remote validation 297 | * Note on Remote validation. 298 | * W3Cjs supports remote file validation but due to some reasons it is not working as expected. 299 | * Local file validation is working perfectly. To overcome this remote page is fetch using 'request' 300 | * npm module and write page content in '_tempvlidation.html' file and validates as local file. 301 | */ 302 | 303 | if (!options.remotePath && options.remoteFiles) { 304 | console.log(msg.remotePathError); 305 | return; 306 | } 307 | 308 | if (options.remotePath && options.remotePath !== '') { 309 | files = makeFileList(files); 310 | } 311 | 312 | if (options.remoteFiles) { 313 | 314 | if (typeof options.remoteFiles === 'object' && options.remoteFiles.length && options.remoteFiles[0] !== '') { 315 | files = options.remoteFiles; 316 | 317 | } else { 318 | files = grunt.file.readJSON(options.remoteFiles); 319 | } 320 | 321 | files = makeFileList(files); 322 | 323 | var dummyFile = files; 324 | 325 | files = []; 326 | 327 | for (var i = 0; i < dummyFile.length; i++) { 328 | files.push('_tempvlidation.html'); 329 | } 330 | 331 | rval(dummyFile[counter], function () { 332 | validate(files); 333 | }); 334 | 335 | return; 336 | } 337 | 338 | if (!options.remoteFiles) { 339 | validate(files); 340 | } 341 | 342 | }); 343 | 344 | }; 345 | -------------------------------------------------------------------------------- /tasks/lib/remoteval.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-html-validation remote validation helper 3 | * https://github.com/praveen/grunt-html-validation 4 | * 5 | * Copyright (c) 2013 - 2014 Praveen Vijayan 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | /* global fileNotFound */ 10 | 11 | 'use strict'; 12 | 13 | module.exports = function remoteval (file, cb) { 14 | 15 | var request = require('request'); 16 | var grunt = require('grunt'); 17 | 18 | request(file, function (error, response, body) { 19 | if (response.statusCode === 404) { 20 | console.log(fileNotFound); 21 | } 22 | 23 | if (!error && response.statusCode === 200) { 24 | grunt.file.write('_tempvlidation.html', body); 25 | return cb(true); 26 | } 27 | }); 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /test/html/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/html/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

New file

19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/html/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

New file

18 | strong 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/html_validation_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.validation = function(test){ 4 | test.expect(1); 5 | test.ok(true, 'initialize testing'); 6 | test.done(); 7 | }; 8 | --------------------------------------------------------------------------------