2121E .update ({
2222 "unrecognized-attribute" :
2323 _ (u"Unrecognized attribute '%(attributeName)s' in <%(tagName)s>" ),
24+ "missing-required-attribute" :
25+ _ (u"Missing required attribute '%(attributeName)s' in <%(tagName)s>" ),
2426})
2527
26- globalAttributes = ['id' , 'title' , 'lang' , 'dir' , 'class' , 'irrelevant' ]
28+ globalAttributes = ['class' , 'contenteditable' , 'contextmenu' , 'dir' ,
29+ 'draggable' , 'id' , 'irrelevant' , 'lang' , 'ref' , 'tabindex' , 'template' ,
30+ 'title' , 'onabort' , 'onbeforeunload' , 'onblur' , 'onchange' , 'onclick' ,
31+ 'oncontextmenu' , 'ondblclick' , 'ondrag' , 'ondragend' , 'ondragenter' ,
32+ 'ondragleave' , 'ondragover' , 'ondragstart' , 'ondrop' , 'onerror' ,
33+ 'onfocus' , 'onkeydown' , 'onkeypress' , 'onkeyup' , 'onload' , 'onmessage' ,
34+ 'onmousedown' , 'onmousemove' , 'onmouseout' , 'onmouseover' , 'onmouseup' ,
35+ 'onmousewheel' , 'onresize' , 'onscroll' , 'onselect' , 'onsubmit' , 'onunload' ]
36+ # XXX lang in HTML only, xml:lang in XHTML only
37+
38+ modAttributes = ['cite' , 'datetime' ]
39+ mediaAttributes = ['src' , 'autoplay' , 'start' , 'loopstart' , 'loopend' , 'end' ,
40+ 'loopcount' , 'controls' ],
2741allowedAttributeMap = {
28- 'html' : globalAttributes + ['xmlns' ]
42+ 'html' : ['xmlns' ],
43+ 'base' : ['href' , 'target' ],
44+ 'link' : ['href' , 'rel' , 'media' , 'hreflang' , 'type' ],
45+ 'meta' : ['name' , 'http-equiv' , 'content' , 'charset' ], # XXX charset in HTML only
46+ 'style' : ['media' , 'type' , 'scoped' ],
47+ 'blockquote' : ['cite' ],
48+ 'ol' : ['start' ],
49+ 'li' : ['value' ], # XXX depends on parent
50+ 'a' : ['href' , 'target' , 'ping' , 'rel' , 'media' , 'hreflang' , 'type' ],
51+ 'q' : ['cite' ],
52+ 'time' : ['datetime' ],
53+ 'meter' : ['value' , 'min' , 'low' , 'high' , 'max' , 'optimum' ],
54+ 'progress' : ['value' , 'max' ],
55+ 'ins' : modAttributes ,
56+ 'del' : modAttributes ,
57+ 'img' : ['alt' , 'src' , 'usemap' , 'ismap' , 'height' , 'width' ], # XXX ismap depends on parent
58+ 'iframe' : ['src' ],
59+ 'object' : ['data' , 'type' , 'usemap' , 'height' , 'width' ],
60+ 'param' : ['name' , 'value' ],
61+ 'video' : mediaAttributes ,
62+ 'audio' : mediaAttributes ,
63+ 'source' : ['src' , 'type' , 'media' ],
64+ 'canvas' : ['height' , 'width' ],
65+ 'area' : ['alt' , 'coords' , 'shape' , 'href' , 'target' , 'ping' , 'rel' ,
66+ 'media' , 'hreflang' , 'type' ],
67+ 'colgroup' : ['span' ], # XXX only if element contains no <col> elements
68+ 'col' : ['span' ],
69+ 'td' : ['colspan' , 'rowspan' ],
70+ 'th' : ['colspan' , 'rowspan' , 'scope' ],
71+ # XXX form elements
72+ 'script' : ['src' , 'defer' , 'async' , 'type' ],
73+ 'event-source' : ['src' ],
74+ 'details' : ['open' ],
75+ 'datagrid' : ['multiple' , 'disabled' ],
76+ 'command' : ['type' , 'label' , 'icon' , 'hidden' , 'disabled' , 'checked' ,
77+ 'radiogroup' , 'default' ],
78+ 'menu' : ['type' , 'label' , 'autosubmit' ],
79+ 'font' : ['style' ]
80+ }
81+
82+ requiredAttributeMap = {
83+ 'link' : ['href' , 'rel' ],
84+ 'bdo' : ['dir' ],
85+ 'img' : ['src' ],
86+ 'embed' : ['src' ],
87+ 'object' : [], # XXX one of 'data' or 'type' is required
88+ 'param' : ['name' , 'value' ],
89+ 'source' : ['src' ],
90+ 'map' : ['id' ],
2991}
3092
3193class HTMLConformanceChecker (_base .Filter ):
@@ -38,13 +100,28 @@ def __iter__(self):
38100 type = token ["type" ]
39101 if type == "StartTag" :
40102 name = token ["name" ].lower ()
41- if name in allowedAttributeMap .keys ():
42- allowedAttributes = allowedAttributeMap [name ]
103+ if name == 'embed' :
104+ # XXX spec says "any attributes w/o namespace"
105+ pass
106+ else :
107+ if name in allowedAttributeMap .keys ():
108+ allowedAttributes = globalAttributes + \
109+ allowedAttributeMap [name ]
110+ else :
111+ allowedAttributes = globalAttributes
43112 for attrName , attrValue in token ["data" ]:
44113 if attrName .lower () not in allowedAttributes :
45114 yield {"type" : "ParseError" ,
46115 "data" : "unrecognized-attribute" ,
47116 "datavars" : {"tagName" : name ,
48117 "attributeName" : attrName }}
49-
118+ if name in requiredAttributeMap .keys ():
119+ attrsPresent = [attrName for attrName , attrValue
120+ in token ["data" ]]
121+ for attrName in requiredAttributeMap [name ]:
122+ if attrName not in attrsPresent :
123+ yield {"type" : "ParseError" ,
124+ "data" : "missing-required-attribute" ,
125+ "datavars" : {"tagName" : name ,
126+ "attributeName" : attrName }}
50127 yield token
0 commit comments