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 for attribute 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 value in token list: '%(attributeValue)s' in '%(attributeName)s' attribute on <%(tagName)s>." ),
4242 "invalid-attribute-value" :
43- _ (u"Invalid value for '%(attributeName)s' attribute on <%(tagName)s>." ),
43+ _ (u"Invalid attribute 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):
237241 if method :
238242 for t in method (token ) or []: yield t
239243 yield token
244+ for t in self .eof () or []: yield t
240245
241246 def checkAttributeValues (self , token ):
242247 tagName = token .get ("name" , "" )
@@ -252,6 +257,28 @@ def checkAttributeValues(self, token):
252257 if method :
253258 for 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+
255282 def validateStartTag (self , token ):
256283 for t in self .checkUnknownStartTag (token ) or []: yield t
257284 for 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+
341372 def 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
354380 if attrValue in self .IDsWeHaveKnownAndLoved :
355381 yield {"type" : "ParseError" ,
356382 "data" : "duplicate-id" ,
357383 "datavars" : {"tagName" : tagName }}
358384 self .IDsWeHaveKnownAndLoved .append (attrValue )
359385 self .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 }}
360393 for c in attrValue :
361394 if c in spaceCharacters :
362395 yield {"type" : "ParseError" ,
363396 "data" : "space-in-id" ,
364- "datavars" : {"tagName" : tagName }}
397+ "datavars" : {"tagName" : tagName ,
398+ "attributeName" : attrName }}
365399 yield {"type" : "ParseError" ,
366400 "data" : "invalid-attribute-value" ,
367401 "datavars" : {"tagName" : tagName ,
0 commit comments