Skip to content

Commit ab8a6bb

Browse files
committed
Patch #912410: Replace HTML entity references for attribute values
in HTMLParser.
1 parent ff432e6 commit ab8a6bb

4 files changed

Lines changed: 43 additions & 13 deletions

File tree

Doc/lib/libhtmlparser.tex

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,18 @@ \section{\module{HTMLParser} ---
7575
be overridden by a derived class; the base class implementation does
7676
nothing.
7777

78-
The \var{tag} argument is the name of the tag converted to
79-
lower case. The \var{attrs} argument is a list of \code{(\var{name},
80-
\var{value})} pairs containing the attributes found inside the tag's
81-
\code{<>} brackets. The \var{name} will be translated to lower case
82-
and double quotes and backslashes in the \var{value} have been
83-
interpreted. For instance, for the tag \code{<A
84-
HREF="http://www.cwi.nl/">}, this method would be called as
78+
The \var{tag} argument is the name of the tag converted to lower case.
79+
The \var{attrs} argument is a list of \code{(\var{name}, \var{value})}
80+
pairs containing the attributes found inside the tag's \code{<>}
81+
brackets. The \var{name} will be translated to lower case, and quotes
82+
in the \var{value} have been removed, and character and entity
83+
references have been replaced. For instance, for the tag \code{<A
84+
HREF="http://www.cwi.nl/">}, this method would be called as
8585
\samp{handle_starttag('a', [('href', 'http://www.cwi.nl/')])}.
86+
87+
\versionchanged[All entity references from htmlentitydefs are now
88+
replaced in the attribute values]{2.6}
89+
8690
\end{methoddesc}
8791
8892
\begin{methoddesc}{handle_startendtag}{tag, attrs}

Lib/HTMLParser.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,30 @@ def unknown_decl(self, data):
358358
self.error("unknown declaration: %r" % (data,))
359359

360360
# Internal -- helper to remove special character quoting
361+
entitydefs = None
361362
def unescape(self, s):
362363
if '&' not in s:
363364
return s
364-
s = s.replace("&lt;", "<")
365-
s = s.replace("&gt;", ">")
366-
s = s.replace("&apos;", "'")
367-
s = s.replace("&quot;", '"')
368-
s = s.replace("&amp;", "&") # Must be last
369-
return s
365+
def replaceEntities(s):
366+
s = s.groups()[0]
367+
if s[0] == "#":
368+
s = s[1:]
369+
if s[0] in ['x','X']:
370+
c = int(s[1:], 16)
371+
else:
372+
c = int(s)
373+
return unichr(c)
374+
else:
375+
# Cannot use name2codepoint directly, because HTMLParser supports apos,
376+
# which is not part of HTML 4
377+
import htmlentitydefs
378+
if HTMLParser.entitydefs is None:
379+
entitydefs = HTMLParser.entitydefs = {'apos':u"'"}
380+
for k, v in htmlentitydefs.name2codepoint.iteritems():
381+
entitydefs[k] = unichr(v)
382+
try:
383+
return self.entitydefs[s]
384+
except KeyError:
385+
return '&'+s+';'
386+
387+
return re.sub(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));", replaceEntities, s)

Lib/test/test_htmlparser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,11 @@ def test_cdata_content(self):
309309
("endtag", "script"),
310310
])
311311

312+
def test_entityrefs_in_attributes(self):
313+
self._run_check("<html foo='&euro;&amp;&#97;&#x61;&unsupported;'>", [
314+
("starttag", "html", [("foo", u"\u20AC&aa&unsupported;")])
315+
])
316+
312317

313318
def test_main():
314319
test_support.run_unittest(HTMLParserTestCase)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ Core and builtins
141141
Library
142142
-------
143143

144+
- Patch #912410: Replace HTML entity references for attribute values
145+
in HTMLParser.
146+
144147
- Patch #1663234: you can now run doctest on test files and modules
145148
using "python -m doctest [-v] filename ...".
146149

0 commit comments

Comments
 (0)