|
41 | 41 | _(u"Duplicate value '%(attributeValue)s' in token list in '%(attributeName)s' attribute on <%(tagName)s>."),
|
42 | 42 | "invalid-attribute-value":
|
43 | 43 | _(u"Invalid value for '%(attributeName)s' attribute on <%(tagName)s>."),
|
| 44 | + "space-in-id": |
| 45 | + _(u"Illegal space character in ID attribute on <%(tagName)s>."), |
| 46 | + "duplicate-id": |
| 47 | + _(u"Duplicate ID on <%(tagName)s>."), |
| 48 | + "attribute-value-can-not-be-blank": |
| 49 | + _(u"Value can not be blank: '%(attributeName)s' attribute on <%(tagName)s>."), |
44 | 50 | })
|
45 | 51 |
|
46 | 52 | globalAttributes = frozenset(('class', 'contenteditable', 'contextmenu', 'dir',
|
@@ -215,6 +221,9 @@ class HTMLConformanceChecker(_base.Filter):
|
215 | 221 | def __init__(self, stream, encoding, parseMeta, **kwargs):
|
216 | 222 | _base.Filter.__init__(self, tokenizer.HTMLTokenizer(
|
217 | 223 | stream, encoding, parseMeta, **kwargs))
|
| 224 | + self.thingsThatDefineAnID = [] |
| 225 | + self.thingsThatPointToAnID = [] |
| 226 | + self.IDsWeHaveKnownAndLoved = [] |
218 | 227 |
|
219 | 228 | def __iter__(self):
|
220 | 229 | for token in _base.Filter.__iter__(self):
|
@@ -329,6 +338,36 @@ def validateAttributeValueContenteditable(self, token, tagName, attrName, attrVa
|
329 | 338 | "datavars": {"tagName": tagName,
|
330 | 339 | "attributeName": attrName}}
|
331 | 340 |
|
| 341 | + def validateAttributeValueId(self, token, tagName, attrName, attrValue): |
| 342 | + # This method has side effects. It adds 'token' to the list of |
| 343 | + # things that define an ID (self.thingsThatDefineAnID) so that we can |
| 344 | + # later check 1) whether an ID is duplicated, and 2) whether all the |
| 345 | + # things that point to something else by ID (like <label for> or |
| 346 | + # <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 | + |
| 354 | + if attrValue in self.IDsWeHaveKnownAndLoved: |
| 355 | + yield {"type": "ParseError", |
| 356 | + "data": "duplicate-id", |
| 357 | + "datavars": {"tagName": tagName}} |
| 358 | + self.IDsWeHaveKnownAndLoved.append(attrValue) |
| 359 | + self.thingsThatDefineAnID.append(token) |
| 360 | + for c in attrValue: |
| 361 | + if c in spaceCharacters: |
| 362 | + yield {"type": "ParseError", |
| 363 | + "data": "space-in-id", |
| 364 | + "datavars": {"tagName": tagName}} |
| 365 | + yield {"type": "ParseError", |
| 366 | + "data": "invalid-attribute-value", |
| 367 | + "datavars": {"tagName": tagName, |
| 368 | + "attributeName": attrName}} |
| 369 | + break |
| 370 | + |
332 | 371 | def checkTokenList(self, tagName, attrName, attrValue):
|
333 | 372 | # The "token" in the method name refers to tokens in an attribute value
|
334 | 373 | # i.e. http://www.whatwg.org/specs/web-apps/current-work/#set-of
|
|
0 commit comments