2020from sets import ImmutableSet as frozenset
2121import _base
2222import iso639codes
23- from html5lib .constants import E ,spaceCharacters
23+ from html5lib .constants import E ,spaceCharacters , digits
2424from html5lib import tokenizer
2525import gettext
2626_ = gettext .gettext
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
6365globalAttributes = frozenset (('class' ,'contenteditable' ,'contextmenu' ,'dir' ,
@@ -250,41 +252,9 @@ def __iter__(self):
250252yield token
251253for 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
289259def validateStartTag (self ,token ):
290260for 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+
327301def checkUnknownStartTag (self ,token ):
328302# check for recognized tag name
329303name = 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+
359351def validateAttributeValueClass (self ,token ,tagName ,attrName ,attrValue ):
360352for t in self .checkTokenList (tagName ,attrName ,attrValue )or []:
361353yield 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-
420380def validateAttributeValueContextmenu (self ,token ,tagName ,attrName ,attrValue ):
421381for t in self .checkIDValue (token ,tagName ,attrName ,attrValue )or []:yield t
422382self .thingsThatPointToAnID .append (token )
@@ -436,6 +396,13 @@ def validateAttributeValueId(self, token, tagName, attrName, attrValue):
436396self .IDsWeHaveKnownAndLoved .append (attrValue )
437397self .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+
439406def checkIDValue (self ,token ,tagName ,attrName ,attrValue ):
440407if not attrValue :
441408yield {"type" :"ParseError" ,
@@ -474,3 +441,102 @@ def checkTokenList(self, tagName, attrName, attrValue):
474441currentValue = ''
475442else :
476443currentValue += 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