3030"unknown-attribute" :
3131_ (u"Unknown '%(attributeName)s' attribute on <%(tagName)s>." ),
3232"missing-required-attribute" :
33- _ (u"Missing required '%(attributeName)s' attribute on <%(tagName)s>." ),
33+ _ (u"The '%(attributeName)s' attribute is required on <%(tagName)s>." ),
3434"unknown-input-type" :
35- _ (u"Illegal value for <input type> attribute: '%(inputType)s'." ),
35+ _ (u"Illegal value forattribute on <input type= '%(inputType)s'> ." ),
3636"attribute-not-allowed-on-this-input-type" :
37- _ (u"'%(attributeName)s' attribute is not allowed on <input type=%(inputType)s>." ),
37+ _ (u"The '%(attributeName)s' attribute is not allowed on <input type=%(inputType)s>." ),
3838"deprecated-attribute" :
39- _ (u"'%(attributeName)s' attribute is deprecated on <%(tagName)s>." ),
39+ _ (u"This attribute is deprecated: '%(attributeName)s' attribute on <%(tagName)s>." ),
4040"duplicate-value-in-token-list" :
41- _ (u"Duplicate value '%(attributeValue)s' in token list in '%(attributeName)s' attribute on <%(tagName)s>." ),
41+ _ (u"Duplicate valuein token list: '%(attributeValue)s' in '%(attributeName)s' attribute on <%(tagName)s>." ),
4242"invalid-attribute-value" :
43- _ (u"Invalidvalue for '%(attributeName)s' attribute on <%(tagName)s>." ),
43+ _ (u"Invalidattribute value: '%(attributeName)s' attribute on <%(tagName)s>." ),
4444"space-in-id" :
45- _ (u"Illegal space character in ID attribute on <%(tagName)s>." ),
45+ _ (u"Whitespace is not allowed here: '%(attributeName)s' attribute on <%(tagName)s>." ),
4646"duplicate-id" :
47- _ (u"Duplicate ID on <%(tagName)s>." ),
47+ _ (u"This ID was already defined earlier: 'id' attribute on <%(tagName)s>." ),
4848"attribute-value-can-not-be-blank" :
49- _ (u"Value can not be blank: '%(attributeName)s' attribute on <%(tagName)s>." ),
49+ _ (u"This value can not be blank: '%(attributeName)s' attribute on <%(tagName)s>." ),
50+ "id-does-not-exist" :
51+ _ (u"This value refers to a non-existent ID: '%(attributeName)s' attribute on <%(tagName)s>." ),
52+ "contextmenu-must-point-to-menu" :
53+ _ (u"The contextmenu attribute must point to an ID defined on a <menu> element." ),
5054})
5155
5256globalAttributes = frozenset (('class' ,'contenteditable' ,'contextmenu' ,'dir' ,
@@ -237,6 +241,7 @@ def __iter__(self):
237241if method :
238242for t in method (token )or []:yield t
239243yield token
244+ for t in self .eof ()or []:yield t
240245
241246def checkAttributeValues (self ,token ):
242247tagName = token .get ("name" ,"" )
@@ -252,6 +257,28 @@ def checkAttributeValues(self, token):
252257if method :
253258for t in method (token ,tagName ,attrName ,attrValue )or []:yield t
254259
260+ def eof (self ):
261+ for token in self .thingsThatPointToAnID :
262+ tagName = token .get ("name" ,"" ).lower ()
263+ attrsDict = token ["data" ]# by now html5parser has "normalized" the attrs list into a dict.
264+ # hooray for obscure side effects!
265+ attrValue = attrsDict .get ("contextmenu" ,"" )
266+ if attrValue and (attrValue not in self .IDsWeHaveKnownAndLoved ):
267+ yield {"type" :"ParseError" ,
268+ "data" :"id-does-not-exist" ,
269+ "datavars" : {"tagName" :tagName ,
270+ "attributeName" :"contextmenu" ,
271+ "attributeValue" :attrValue }}
272+ else :
273+ for refToken in self .thingsThatDefineAnID :
274+ id = refToken .get ("data" , {}).get ("id" ,"" )
275+ if not id :continue
276+ if id == attrValue :
277+ if refToken .get ("name" ,"" ).lower ()!= "menu" :
278+ yield {"type" :"ParseError" ,
279+ "data" :"contextmenu-must-point-to-menu" }
280+ break
281+
255282def validateStartTag (self ,token ):
256283for t in self .checkUnknownStartTag (token )or []:yield t
257284for t in self .checkStartTagRequiredAttributes (token )or []:yield t
@@ -338,30 +365,37 @@ def validateAttributeValueContenteditable(self, token, tagName, attrName, attrVa
338365"datavars" : {"tagName" :tagName ,
339366"attributeName" :attrName }}
340367
368+ def validateAttributeValueContextmenu (self ,token ,tagName ,attrName ,attrValue ):
369+ for t in self .checkIDValue (token ,tagName ,attrName ,attrValue )or []:yield t
370+ self .thingsThatPointToAnID .append (token )
371+
341372def validateAttributeValueId (self ,token ,tagName ,attrName ,attrValue ):
342373# This method has side effects. It adds 'token' to the list of
343374# things that define an ID (self.thingsThatDefineAnID) so that we can
344375# later check 1) whether an ID is duplicated, and 2) whether all the
345376# things that point to something else by ID (like <label for> or
346377# <span contextmenu>) point to an ID that actually exists somewhere.
347- if not attrValue :
348- yield {"type" :"ParseError" ,
349- "data" :"attribute-value-can-not-be-blank" ,
350- "datavars" : {"tagName" :tagName ,
351- "attributeName" :attrName }}
352- return
353-
378+ for t in self .checkIDValue (token ,tagName ,attrName ,attrValue )or []:yield t
379+ if not attrValue :return
354380if attrValue in self .IDsWeHaveKnownAndLoved :
355381yield {"type" :"ParseError" ,
356382"data" :"duplicate-id" ,
357383"datavars" : {"tagName" :tagName }}
358384self .IDsWeHaveKnownAndLoved .append (attrValue )
359385self .thingsThatDefineAnID .append (token )
386+
387+ def checkIDValue (self ,token ,tagName ,attrName ,attrValue ):
388+ if not attrValue :
389+ yield {"type" :"ParseError" ,
390+ "data" :"attribute-value-can-not-be-blank" ,
391+ "datavars" : {"tagName" :tagName ,
392+ "attributeName" :attrName }}
360393for c in attrValue :
361394if c in spaceCharacters :
362395yield {"type" :"ParseError" ,
363396"data" :"space-in-id" ,
364- "datavars" : {"tagName" :tagName }}
397+ "datavars" : {"tagName" :tagName ,
398+ "attributeName" :attrName }}
365399yield {"type" :"ParseError" ,
366400"data" :"invalid-attribute-value" ,
367401"datavars" : {"tagName" :tagName ,