fortune_html_parser.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. # -*- coding: utf-8
  2. import re
  3. from HTMLParser import HTMLParser
  4. from difflib import unified_diff
  5. class FortuneHTMLParser(HTMLParser):
  6. body = []
  7. valid = '''<!doctype html><html>
  8. <head><title>Fortunes</title></head>
  9. <body><table>
  10. <tr><th>id</th><th>message</th></tr>
  11. <tr><td>11</td><td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;/script&gt;</td></tr>
  12. <tr><td>4</td><td>A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1</td></tr>
  13. <tr><td>5</td><td>A computer program does what you tell it to do, not what you want it to do.</td></tr>
  14. <tr><td>2</td><td>A computer scientist is someone who fixes things that aren&apos;t broken.</td></tr>
  15. <tr><td>8</td><td>A list is only as strong as its weakest link. — Donald Knuth</td></tr>
  16. <tr><td>0</td><td>Additional fortune added at request time.</td></tr>
  17. <tr><td>3</td><td>After enough decimal places, nobody gives a damn.</td></tr>
  18. <tr><td>7</td><td>Any program that runs right is obsolete.</td></tr>
  19. <tr><td>10</td><td>Computers make very fast, very accurate mistakes.</td></tr>
  20. <tr><td>6</td><td>Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen</td></tr>
  21. <tr><td>9</td><td>Feature: A bug with seniority.</td></tr>
  22. <tr><td>1</td><td>fortune: No such file or directory</td></tr>
  23. <tr><td>12</td><td>フレームワークのベンチマーク</td></tr>
  24. </table></body></html>'''
  25. # Is called when a doctype or other such tag is read in.
  26. # For our purposes, we assume this is only going to be
  27. # "DOCTYPE html", so we will surround it with "<!" and ">".
  28. def handle_decl(self, decl):
  29. # The spec says that for HTML this is case insensitive,
  30. # and since we did not specify xml compliance (where
  31. # incorrect casing would throw a syntax error), we must
  32. # allow all casings. We will lower for our normalization.
  33. self.body.append("<!{d}>".format(d=decl.lower()))
  34. # This is called when an HTML character is parsed (i.e.
  35. # &quot;). There are a number of issues to be resolved
  36. # here. For instance, some tests choose to leave the
  37. # "+" character as-is, which should be fine as far as
  38. # character escaping goes, but others choose to use the
  39. # character reference of "&#43;", which is also fine.
  40. # Therefore, this method looks for all possible character
  41. # references and normalizes them so that we can
  42. # validate the input against a single valid spec string.
  43. # Another example problem: "&quot;" is valid, but so is
  44. # "&#34;"
  45. def handle_charref(self, name):
  46. val = name.lower()
  47. # "&#34;" is a valid escaping, but we are normalizing
  48. # it so that our final parse can just be checked for
  49. # equality.
  50. if val == "34" or val == "034" or val == "x22":
  51. # Append our normalized entity reference to our body.
  52. self.body.append("&quot;")
  53. # "&#39;" is a valid escaping of "-", but it is not
  54. # required, so we normalize for equality checking.
  55. if val == "39" or val == "039" or val == "x27":
  56. self.body.append("&apos;")
  57. # Again, "&#43;" is a valid escaping of the "+", but
  58. # it is not required, so we need to normalize for out
  59. # final parse and equality check.
  60. if val == "43" or val == "043" or val == "x2b":
  61. self.body.append("+")
  62. # Again, "&#62;" is a valid escaping of ">", but we
  63. # need to normalize to "&gt;" for equality checking.
  64. if val == "62" or val == "062" or val == "x3e":
  65. self.body.append("&gt;")
  66. # Again, "&#60;" is a valid escaping of "<", but we
  67. # need to normalize to "&lt;" for equality checking.
  68. if val == "60" or val == "060" or val == "x3c":
  69. self.body.append("&lt;")
  70. # Not sure why some are escaping '/'
  71. if val == "47" or val == "047" or val == "x2f":
  72. self.body.append("/")
  73. def handle_entityref(self, name):
  74. # Again, "&mdash;" is a valid escaping of "—", but we
  75. # need to normalize to "—" for equality checking.
  76. if name == "mdash":
  77. self.body.append("—")
  78. else:
  79. self.body.append("&{n};".format(n=name))
  80. # This is called every time a tag is opened. We append
  81. # each one wrapped in "<" and ">".
  82. def handle_starttag(self, tag, attrs):
  83. self.body.append("<{t}>".format(t=tag))
  84. # Append a newline after the <table> and <html>
  85. if tag.lower() == 'table' or tag.lower() == 'html':
  86. self.body.append("\n")
  87. # This is called whenever data is presented inside of a
  88. # start and end tag. Generally, this will only ever be
  89. # the contents inside of "<td>" and "</td>", but there
  90. # are also the "<title>" and "</title>" tags.
  91. def handle_data (self, data):
  92. if data.strip() != '':
  93. # After a LOT of debate, these are now considered
  94. # valid in data. The reason for this approach is
  95. # because a few tests use tools which determine
  96. # at compile time whether or not a string needs
  97. # a given type of html escaping, and our fortune
  98. # test has apostrophes and quotes in html data
  99. # rather than as an html attribute etc.
  100. # example:
  101. # <td>A computer scientist is someone who fixes things that aren't broken.</td>
  102. # Semanticly, that apostrophe does not NEED to
  103. # be escaped. The same is currently true for our
  104. # quotes.
  105. # In fact, in data (read: between two html tags)
  106. # even the '>' need not be replaced as long as
  107. # the '<' are all escaped.
  108. # We replace them with their escapings here in
  109. # order to have a noramlized string for equality
  110. # comparison at the end.
  111. data = data.replace('\'', '&apos;')
  112. data = data.replace('"', '&quot;')
  113. data = data.replace('>', '&gt;')
  114. self.body.append("{d}".format(d=data))
  115. # This is called every time a tag is closed. We append
  116. # each one wrapped in "</" and ">".
  117. def handle_endtag(self, tag):
  118. self.body.append("</{t}>".format(t=tag))
  119. # Append a newline after each </tr> and </head>
  120. if tag.lower() == 'tr' or tag.lower() == 'head':
  121. self.body.append("\n")
  122. # Returns whether the HTML input parsed by this parser
  123. # is valid against our known "fortune" spec.
  124. # The parsed data in 'body' is joined on empty strings
  125. # and checked for equality against our spec.
  126. def isValidFortune(self, out):
  127. body = ''.join(self.body)
  128. same = self.valid == body
  129. diff_lines = []
  130. if not same:
  131. out.write("Oh no! I compared %s\n\n\nto.....%s" % (self.valid, body))
  132. out.write("Fortune invalid. Diff following:\n")
  133. headers_left = 3
  134. for line in unified_diff(self.valid.split('\n'), body.split('\n'), fromfile='Valid', tofile='Response', n=0):
  135. diff_lines.append(line)
  136. out.write(line)
  137. headers_left -= 1
  138. if headers_left <= 0:
  139. out.write('\n')
  140. return (same, diff_lines)