#!/usr/bin/env python
"""HtmlValidatorMiddleware for Django
See http://bstpierre.org/Projects/HtmlValidatorMiddleware
Required: Python 2.4 or later
Required: validate, the Offline HTMLHelp.com Validator (or equivalent)
"""
__version__ = "1.1"
__license__ = """Copyright (c) 2007, Brian St. Pierre, All rights reserved.
Permission to use, copy, modify, and distribute this software and
its documentation for any purpose, without fee, and without a written
agreement is hereby granted, provided that the above copyright notice
and this paragraph and the following two paragraphs appear in all copies.
IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
"""
__author__ = "Brian St. Pierre "
import math
from subprocess import *
from django.utils.html import escape
from django.http import HttpResponseServerError
from django.template import Context, Template
# Just drop this middleware into your project, include it in your
# settings file (near the end, but before gzip), and any time a page
# does not validate, this middleware will send back a 500 error page
# containing the output from `validate` and the html source annotated
# with line numbers.
#
# This does not put anything into your database, it just squawks if
# you get a validation error.
# Customize this template if you want to.
ERROR_MESSAGE_TEMPLATE = '''
VALIDATION ERROR
Error: HTML does not validate
This notice brought to you by
HtmlValidatorMiddleware.
{{ error_message }}
{{ escaped_source }}
'''
class HtmlValidatorMiddleware:
def escaped_source(self, content):
'''Return the given content with html escaped and line
numbers in the left margin.'''
# This is overkill, but it's nice to know that no matter how
# long the content is, we'll have enough leading zeros so that
# all of the numbers line up!
lines = content.split('\n')
format = '%%0%dd: %%s\n' % int(math.ceil(math.log(len(lines), 10)))
output = ''
n = 1
for line in lines:
output += format % (n, escape(line))
n += 1
return output
def process_response(self, request, response):
# Don't validate error pages.
# Don't try to validate if it isn't html.
if (response.status_code != 200 or
response['Content-Type'] != 'text/html'):
return response
p = Popen(['validate'], stdin=PIPE, stdout=PIPE)
p.stdin.write(response.content)
p.stdin.close()
output = p.stdout.read()
if len(output) > 0:
## The output is invalid! Modify the response to include
## the error message(s) and the escaped source.
t = Template(ERROR_MESSAGE_TEMPLATE)
c = Context({
'error_message': output + `response.headers`,
'escaped_source': self.escaped_source(response.content),
})
error_response = HttpResponseServerError(t.render(c),
mimetype='text/html')
return error_response
else:
return response