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):
38100type = token ["type" ]
39101if type == "StartTag" :
40102name = 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
43112for attrName ,attrValue in token ["data" ]:
44113if attrName .lower ()not in allowedAttributes :
45114yield {"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 }}
50127yield token