Skip to content

Commit d559d9e

Browse files
author
Mark Pilgrim
committed
added support for validating tabindex attribute
--HG-- extra : convert_revision : svn%3Aacbfec75-9323-0410-a652-858a13e371e0/trunk%40984
1 parent 0a4912b commit d559d9e

File tree

1 file changed

+134
-68
lines changed

1 file changed

+134
-68
lines changed

src/html5lib/filters/validator.py

Lines changed: 134 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from sets import ImmutableSet as frozenset
2121
import _base
2222
import iso639codes
23-
from html5lib.constants import E, spaceCharacters
23+
from html5lib.constants import E, spaceCharacters, digits
2424
from html5lib import tokenizer
2525
import gettext
2626
_ = gettext.gettext
@@ -58,6 +58,8 @@
5858
_(u"The contextmenu attribute must point to an ID defined on a <menu> element."),
5959
"invalid-lang-code":
6060
_(u"Invalid language code: '%(attributeName)s' attibute on <%(tagName)s>."),
61+
"invalid-integer-value":
62+
_(u"Value must be an integer: '%(attributeName)s' attribute on <%tagName)s>."),
6163
})
6264

6365
globalAttributes = frozenset(('class', 'contenteditable', 'contextmenu', 'dir',
@@ -250,41 +252,9 @@ def __iter__(self):
250252
yield token
251253
for t in self.eof() or []: yield t
252254

253-
def checkAttributeValues(self, token):
254-
tagName = token.get("name", "")
255-
fakeToken = {"tagName": tagName.capitalize()}
256-
for attrName, attrValue in token.get("data", []):
257-
attrName = attrName.lower()
258-
fakeToken["attributeName"] = attrName.capitalize()
259-
method = getattr(self, "validateAttributeValue%(tagName)s%(attributeName)s" % fakeToken, None)
260-
if method:
261-
for t in method(token, tagName, attrName, attrValue) or []: yield t
262-
else:
263-
method = getattr(self, "validateAttributeValue%(attributeName)s" % fakeToken, None)
264-
if method:
265-
for t in method(token, tagName, attrName, attrValue) or []: yield t
266-
267-
def eof(self):
268-
for token in self.thingsThatPointToAnID:
269-
tagName = token.get("name", "").lower()
270-
attrsDict = token["data"] # by now html5parser has "normalized" the attrs list into a dict.
271-
# hooray for obscure side effects!
272-
attrValue = attrsDict.get("contextmenu", "")
273-
if attrValue and (attrValue not in self.IDsWeHaveKnownAndLoved):
274-
yield {"type": "ParseError",
275-
"data": "id-does-not-exist",
276-
"datavars": {"tagName": tagName,
277-
"attributeName": "contextmenu",
278-
"attributeValue": attrValue}}
279-
else:
280-
for refToken in self.thingsThatDefineAnID:
281-
id = refToken.get("data", {}).get("id", "")
282-
if not id: continue
283-
if id == attrValue:
284-
if refToken.get("name", "").lower() != "menu":
285-
yield {"type": "ParseError",
286-
"data": "contextmenu-must-point-to-menu"}
287-
break
255+
##########################################################################
256+
# Start tag validation
257+
##########################################################################
288258

289259
def validateStartTag(self, token):
290260
for t in self.checkUnknownStartTag(token) or []: yield t
@@ -324,6 +294,10 @@ def validateStartTagInput(self, token):
324294
"datavars": {"attributeName": attrName,
325295
"inputType": inputType}}
326296

297+
##########################################################################
298+
# Start tag validation helpers
299+
##########################################################################
300+
327301
def checkUnknownStartTag(self, token):
328302
# check for recognized tag name
329303
name = token.get("name", "").lower()
@@ -356,6 +330,24 @@ def checkStartTagUnknownAttributes(self, token):
356330
"datavars": {"tagName": name,
357331
"attributeName": attrName}}
358332

333+
##########################################################################
334+
# Attribute validation
335+
##########################################################################
336+
337+
def checkAttributeValues(self, token):
338+
tagName = token.get("name", "")
339+
fakeToken = {"tagName": tagName.capitalize()}
340+
for attrName, attrValue in token.get("data", []):
341+
attrName = attrName.lower()
342+
fakeToken["attributeName"] = attrName.capitalize()
343+
method = getattr(self, "validateAttributeValue%(tagName)s%(attributeName)s" % fakeToken, None)
344+
if method:
345+
for t in method(token, tagName, attrName, attrValue) or []: yield t
346+
else:
347+
method = getattr(self, "validateAttributeValue%(attributeName)s" % fakeToken, None)
348+
if method:
349+
for t in method(token, tagName, attrName, attrValue) or []: yield t
350+
359351
def validateAttributeValueClass(self, token, tagName, attrName, attrValue):
360352
for t in self.checkTokenList(tagName, attrName, attrValue) or []:
361353
yield t
@@ -385,38 +377,6 @@ def validateAttributeValueLang(self, token, tagName, attrName, attrValue):
385377
"attributeName": attrName,
386378
"attributeValue": attrValue}}
387379

388-
def checkEnumeratedValue(self, token, tagName, attrName, attrValue, enumeratedValues):
389-
if not attrValue and ('' not in enumeratedValues):
390-
yield {"type": "ParseError",
391-
"data": "attribute-value-can-not-be-blank",
392-
"datavars": {"tagName": tagName,
393-
"attributeName": attrName}}
394-
return
395-
attrValue = attrValue.lower()
396-
if attrValue not in enumeratedValues:
397-
yield {"type": "ParseError",
398-
"data": "invalid-enumerated-value",
399-
"datavars": {"tagName": tagName,
400-
"attributeName": attrName,
401-
"enumeratedValues": tuple(enumeratedValues)}}
402-
yield {"type": "ParseError",
403-
"data": "invalid-attribute-value",
404-
"datavars": {"tagName": tagName,
405-
"attributeName": attrName}}
406-
407-
def checkBooleanValue(self, token, tagName, attrName, attrValue):
408-
enumeratedValues = frozenset((attrName, ''))
409-
if attrValue not in enumeratedValues:
410-
yield {"type": "ParseError",
411-
"data": "invalid-boolean-value",
412-
"datavars": {"tagName": tagName,
413-
"attributeName": attrName,
414-
"enumeratedValues": tuple(enumeratedValues)}}
415-
yield {"type": "ParseError",
416-
"data": "invalid-attribute-value",
417-
"datavars": {"tagName": tagName,
418-
"attributeName": attrName}}
419-
420380
def validateAttributeValueContextmenu(self, token, tagName, attrName, attrValue):
421381
for t in self.checkIDValue(token, tagName, attrName, attrValue) or []: yield t
422382
self.thingsThatPointToAnID.append(token)
@@ -436,6 +396,13 @@ def validateAttributeValueId(self, token, tagName, attrName, attrValue):
436396
self.IDsWeHaveKnownAndLoved.append(attrValue)
437397
self.thingsThatDefineAnID.append(token)
438398

399+
def validateAttributeValueTabindex(self, token, tagName, attrName, attrValue):
400+
for t in self.checkIntegerValue(token, tagName, attrName, attrValue) or []: yield t
401+
402+
##########################################################################
403+
# Attribute validation helpers
404+
##########################################################################
405+
439406
def checkIDValue(self, token, tagName, attrName, attrValue):
440407
if not attrValue:
441408
yield {"type": "ParseError",
@@ -474,3 +441,102 @@ def checkTokenList(self, tagName, attrName, attrValue):
474441
currentValue = ''
475442
else:
476443
currentValue += c
444+
445+
def checkEnumeratedValue(self, token, tagName, attrName, attrValue, enumeratedValues):
446+
if not attrValue and ('' not in enumeratedValues):
447+
yield {"type": "ParseError",
448+
"data": "attribute-value-can-not-be-blank",
449+
"datavars": {"tagName": tagName,
450+
"attributeName": attrName}}
451+
return
452+
attrValue = attrValue.lower()
453+
if attrValue not in enumeratedValues:
454+
yield {"type": "ParseError",
455+
"data": "invalid-enumerated-value",
456+
"datavars": {"tagName": tagName,
457+
"attributeName": attrName,
458+
"enumeratedValues": tuple(enumeratedValues)}}
459+
yield {"type": "ParseError",
460+
"data": "invalid-attribute-value",
461+
"datavars": {"tagName": tagName,
462+
"attributeName": attrName}}
463+
464+
def checkBooleanValue(self, token, tagName, attrName, attrValue):
465+
enumeratedValues = frozenset((attrName, ''))
466+
if attrValue not in enumeratedValues:
467+
yield {"type": "ParseError",
468+
"data": "invalid-boolean-value",
469+
"datavars": {"tagName": tagName,
470+
"attributeName": attrName,
471+
"enumeratedValues": tuple(enumeratedValues)}}
472+
yield {"type": "ParseError",
473+
"data": "invalid-attribute-value",
474+
"datavars": {"tagName": tagName,
475+
"attributeName": attrName}}
476+
477+
def checkIntegerValue(self, token, tagName, attrName, attrValue):
478+
sign = 1
479+
numberString = ''
480+
state = 'begin' # ('begin', 'initial-number', 'number', 'trailing-junk')
481+
error = {"type": "ParseError",
482+
"data": "invalid-integer-value",
483+
"datavars": {"tagName": tagName,
484+
"attributeName": attrName,
485+
"attributeValue": attrValue}}
486+
for c in attrValue:
487+
if state == 'begin':
488+
if c in spaceCharacters:
489+
pass
490+
elif c == '-':
491+
sign = -1
492+
state = 'initial-number'
493+
elif c in digits:
494+
numberString += c
495+
state = 'in-number'
496+
else:
497+
yield error
498+
return
499+
elif state == 'initial-number':
500+
if c not in digits:
501+
yield error
502+
return
503+
numberString += c
504+
state = 'in-number'
505+
elif state == 'in-number':
506+
if c in digits:
507+
numberString += c
508+
else:
509+
state = 'trailing-junk'
510+
elif state == 'trailing-junk':
511+
pass
512+
if not numberString:
513+
yield {"type": "ParseError",
514+
"data": "attribute-value-can-not-be-blank",
515+
"datavars": {"tagName": tagName,
516+
"attributeName": attrName}}
517+
518+
##########################################################################
519+
# Whole document validation (IDs, etc.)
520+
##########################################################################
521+
522+
def eof(self):
523+
for token in self.thingsThatPointToAnID:
524+
tagName = token.get("name", "").lower()
525+
attrsDict = token["data"] # by now html5parser has "normalized" the attrs list into a dict.
526+
# hooray for obscure side effects!
527+
attrValue = attrsDict.get("contextmenu", "")
528+
if attrValue and (attrValue not in self.IDsWeHaveKnownAndLoved):
529+
yield {"type": "ParseError",
530+
"data": "id-does-not-exist",
531+
"datavars": {"tagName": tagName,
532+
"attributeName": "contextmenu",
533+
"attributeValue": attrValue}}
534+
else:
535+
for refToken in self.thingsThatDefineAnID:
536+
id = refToken.get("data", {}).get("id", "")
537+
if not id: continue
538+
if id == attrValue:
539+
if refToken.get("name", "").lower() != "menu":
540+
yield {"type": "ParseError",
541+
"data": "contextmenu-must-point-to-menu"}
542+
break

0 commit comments

Comments
 (0)