Merge lp:~wgrant/launchpad/wbr-is-void into lp:launchpad

Proposed by William Grant on 2012-04-02
Status: Merged
Approved by: William Grant on 2012-04-02
Approved revision: no longer in the source branch.
Merged at revision: 15051
Proposed branch: lp:~wgrant/launchpad/wbr-is-void
Merge into: lp:launchpad
Diff against target: 448 lines (+99/-93)
10 files modified
lib/lp/app/__init__.py (+8/-2)
lib/lp/app/browser/stringformatter.py (+2/-2)
lib/lp/app/browser/tests/test_stringformatter.py (+23/-23)
lib/lp/app/doc/displaying-paragraphs-of-text.txt (+45/-45)
lib/lp/app/doc/tales-email-formatting.txt (+10/-10)
lib/lp/app/doc/tales.txt (+5/-5)
lib/lp/blueprints/workitemmigration.py (+1/-1)
lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt (+2/-2)
lib/lp/registry/browser/tests/person-views.txt (+2/-2)
lib/lp/registry/stories/announcements/xx-announcements.txt (+1/-1)
To merge this branch: bzr merge lp:~wgrant/launchpad/wbr-is-void
Reviewer Review Type Date Requested Status
Ian Booth (community) 2012-04-02 Approve on 2012-04-02
Review via email: mp+100357@code.launchpad.net

Commit Message

Use a self-closing form of <wbr> to be a valid HTML/XML polyglot. It's a void element, so a separate closing tag is invalid.

Description of the Change

Launchpad currently spits out <wbr></wbr> sequences instead of the classical <wbr>. Amusingly, while having a separate closing tag makes it well-formed XML, because it's a void element it's neither valid HTML nor XHTML.

We should use <wbr /> instead. HTML5 and XML both like that, as do all the browsers we support.

The only thing I've found that doesn't is BeautifulSoup; it assumes the closing tag was forgotten, so extends the element to the next tag. Even if a browser I haven't tested does the same thing, the tag will just be ignored harmlessly.

This makes a lot more of our pages valid HTML5 while remaining well-formed XML.

To post a comment you must log in.
Ian Booth (wallyworld) wrote :

Seems ok, so long as an XXX is added for the BeautifulSoup workaround.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/__init__.py'
2--- lib/lp/app/__init__.py 2011-12-19 23:38:16 +0000
3+++ lib/lp/app/__init__.py 2012-04-02 11:42:25 +0000
4@@ -15,10 +15,16 @@
5 # values, but they kindly left this global variable for you to monkey patch if
6 # you want the old behavior, just like we do.
7 from zope.app.form.browser import itemswidgets
8-
9-
10 itemswidgets.EXPLICIT_EMPTY_SELECTION = False
11
12+# Monkeypatch our embedded BeautifulSoup and the one in mechanize to
13+# teach them that wbr (new in HTML5, but widely supported forever) is
14+# self-closing.
15+import BeautifulSoup
16+import mechanize._beautifulsoup
17+BeautifulSoup.BeautifulSoup.SELF_CLOSING_TAGS['wbr'] = None
18+mechanize._beautifulsoup.BeautifulSoup.SELF_CLOSING_TAGS['wbr'] = None
19+
20 # Load versioninfo.py so that we get errors on start-up rather than waiting
21 # for first page load.
22 import lp.app.versioninfo
23
24=== modified file 'lib/lp/app/browser/stringformatter.py'
25--- lib/lp/app/browser/stringformatter.py 2011-12-29 05:29:36 +0000
26+++ lib/lp/app/browser/stringformatter.py 2012-04-02 11:42:25 +0000
27@@ -153,7 +153,7 @@
28 while pos < len(word):
29 chunk, pos = next_word_chunk(word, pos, 7, 15)
30 broken.append(chunk)
31- return '<wbr></wbr>'.join(broken)
32+ return '<wbr />'.join(broken)
33
34
35 break_text_pat = re.compile(r'''
36@@ -718,7 +718,7 @@
37 return line[:-4]
38
39 for line in self.text_to_html().split('\n'):
40- if 'Desired=<wbr></wbr>Unknown/' in line and not in_fold:
41+ if 'Desired=<wbr />Unknown/' in line and not in_fold:
42 # When we see a evidence of dpkg output, we switch the
43 # quote matching rules. We do not assume lines that start
44 # with a pipe are quoted passages. dpkg output is often
45
46=== modified file 'lib/lp/app/browser/tests/test_stringformatter.py'
47--- lib/lp/app/browser/tests/test_stringformatter.py 2012-01-01 02:58:52 +0000
48+++ lib/lp/app/browser/tests/test_stringformatter.py 2012-04-02 11:42:25 +0000
49@@ -73,18 +73,18 @@
50 >>> from lp.app.browser.stringformatter import add_word_breaks
51
52 >>> print add_word_breaks('abcdefghijklmnop')
53- abcdefghijklmno<wbr></wbr>p
54+ abcdefghijklmno<wbr />p
55
56 >>> print add_word_breaks('abcdef/ghijklmnop')
57- abcdef/<wbr></wbr>ghijklmnop
58+ abcdef/<wbr />ghijklmnop
59
60 >>> print add_word_breaks('ab/cdefghijklmnop')
61- ab/cdefghijklmn<wbr></wbr>op
62+ ab/cdefghijklmn<wbr />op
63
64 The string can contain HTML entities, which do not get split:
65
66 >>> print add_word_breaks('abcdef&anentity;hijklmnop')
67- abcdef&anentity;<wbr></wbr>hijklmnop
68+ abcdef&anentity;<wbr />hijklmnop
69 """
70
71
72@@ -100,13 +100,13 @@
73 1234567890123456
74
75 >>> print break_long_words('12345678901234567890')
76- 123456789012345<wbr></wbr>67890
77+ 123456789012345<wbr />67890
78
79 >>> print break_long_words('<tag a12345678901234567890="foo"></tag>')
80 <tag a12345678901234567890="foo"></tag>
81
82 >>> print break_long_words('12345678901234567890 1234567890.1234567890')
83- 123456789012345<wbr></wbr>67890 1234567890.<wbr></wbr>1234567890
84+ 123456789012345<wbr />67890 1234567890.<wbr />1234567890
85
86 >>> print break_long_words('1234567890&abcdefghi;123')
87 1234567890&abcdefghi;123
88@@ -172,13 +172,13 @@
89
90 expected_strings = [
91 ('<p><a rel="nofollow" href="http://example.com">'
92- 'http://<wbr></wbr>example.<wbr></wbr>com</a></p>'),
93+ 'http://<wbr />example.<wbr />com</a></p>'),
94 ('<p><a rel="nofollow" href="http://example.com/">'
95- 'http://<wbr></wbr>example.<wbr></wbr>com/</a></p>'),
96+ 'http://<wbr />example.<wbr />com/</a></p>'),
97 ('<p><a rel="nofollow" href="http://example.com/path">'
98- 'http://<wbr></wbr>example.<wbr></wbr>com/path</a></p>'),
99+ 'http://<wbr />example.<wbr />com/path</a></p>'),
100 ('<p><a rel="nofollow" href="http://example.com/path/">'
101- 'http://<wbr></wbr>example.<wbr></wbr>com/path/</a></p>'),
102+ 'http://<wbr />example.<wbr />com/path/</a></p>'),
103 ]
104
105 self.assertEqual(
106@@ -196,23 +196,23 @@
107
108 expected_html = [
109 ('<p>(<a rel="nofollow" href="http://example.com">'
110- 'http://<wbr></wbr>example.<wbr></wbr>com</a>)</p>'),
111+ 'http://<wbr />example.<wbr />com</a>)</p>'),
112 ('<p><a rel="nofollow" '
113 'href="http://example.com/path_(with_parens)">'
114- 'http://<wbr></wbr>example.<wbr></wbr>com/path_'
115- '<wbr></wbr>(with_parens)</a></p>'),
116+ 'http://<wbr />example.<wbr />com/path_'
117+ '<wbr />(with_parens)</a></p>'),
118 ('<p>(<a rel="nofollow" '
119 'href="http://example.com/path_(with_parens)">'
120- 'http://<wbr></wbr>example.<wbr></wbr>com/path_'
121- '<wbr></wbr>(with_parens)</a>)</p>'),
122+ 'http://<wbr />example.<wbr />com/path_'
123+ '<wbr />(with_parens)</a>)</p>'),
124 ('<p>(<a rel="nofollow" '
125 'href="http://example.com/path_(with_parens)and_stuff">'
126- 'http://<wbr></wbr>example.<wbr></wbr>com'
127- '/path_<wbr></wbr>(with_parens)<wbr></wbr>and_stuff</a>)</p>'),
128+ 'http://<wbr />example.<wbr />com'
129+ '/path_<wbr />(with_parens)<wbr />and_stuff</a>)</p>'),
130 ('<p><a rel="nofollow" '
131 'href="http://example.com/path_(with_parens">'
132- 'http://<wbr></wbr>example.<wbr></wbr>com'
133- '/path_<wbr></wbr>(with_parens</a></p>'),
134+ 'http://<wbr />example.<wbr />com'
135+ '/path_<wbr />(with_parens</a></p>'),
136 ]
137
138 self.assertEqual(
139@@ -236,7 +236,7 @@
140 expected_html = (
141 '<p>This becomes a link: '
142 '<a rel="nofollow" '
143- 'href="apt:some-package">apt:some-<wbr></wbr>package</a></p>')
144+ 'href="apt:some-package">apt:some-<wbr />package</a></p>')
145 self.assertEqual(expected_html, html)
146
147 # Do it again for apt://
148@@ -245,7 +245,7 @@
149 expected_html = (
150 '<p>This becomes a link: '
151 '<a rel="nofollow" '
152- 'href="apt://some-package">apt://some-<wbr></wbr>package</a></p>')
153+ 'href="apt://some-package">apt://some-<wbr />package</a></p>')
154 self.assertEqual(expected_html, html)
155
156 def test_file_is_not_linked(self):
157@@ -253,7 +253,7 @@
158 html = FormattersAPI(test_string).text_to_html()
159 expected_html = (
160 "<p>This doesn't become a link: "
161- "file://<wbr></wbr>some/file.<wbr></wbr>txt</p>")
162+ "file://<wbr />some/file.<wbr />txt</p>")
163 self.assertEqual(expected_html, html)
164
165 def test_data_is_linked(self):
166@@ -263,7 +263,7 @@
167 "<p>This becomes a link: "
168 '<a rel="nofollow" '
169 'href="data:text/plain,test">'
170- 'data:text/<wbr></wbr>plain,test</a></p>')
171+ 'data:text/<wbr />plain,test</a></p>')
172 self.assertEqual(expected_html, html)
173
174 def test_no_link_with_linkify_text_false(self):
175
176=== modified file 'lib/lp/app/doc/displaying-paragraphs-of-text.txt'
177--- lib/lp/app/doc/displaying-paragraphs-of-text.txt 2011-12-29 05:29:36 +0000
178+++ lib/lp/app/doc/displaying-paragraphs-of-text.txt 2012-04-02 11:42:25 +0000
179@@ -85,7 +85,7 @@
180 <p>This is a code sample written in Python.<br />
181 &nbsp;&nbsp;&nbsp;&nbsp;def messageCount(self):<br />
182 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"""See IRosettaStats."""<br />
183- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self.potemplate<wbr></wbr>.messageCount(<wbr></wbr>)</p>
184+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self.potemplate<wbr />.messageCount(<wbr />)</p>
185 <p>&nbsp;&nbsp;&nbsp;&nbsp;def currentCount(self, language=None):<br />
186 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"""See IRosettaStats."""<br />
187 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return self.currentCount</p>
188@@ -103,12 +103,12 @@
189 ... 'I have a Jabber account (jabber:foo@jabber.example.com)\n'
190 ... 'Foo Bar <mailto:foo.bar@example.net>')
191 >>> print test_tales('foo/fmt:text-to-html', foo=text)
192- <p><a rel="nofollow" href="https://launchpad.net/">https:/<wbr></wbr>/launchpad.<wbr></wbr>net/</a> is the new Launchpad site<br />
193- <a rel="nofollow" href="http://example.com/something?foo=bar&amp;hum=baz">http://<wbr></wbr>example.<wbr></wbr>com/something?<wbr></wbr>foo=bar&amp;<wbr></wbr>hum=baz</a><br />
194- You can check the PPC md5sums at <a rel="nofollow" href="ftp://ftp.ubuntu.com/ubuntu/dists/breezy/main/installer-powerpc/current/images/MD5SUMS">ftp://ftp.<wbr></wbr>ubuntu.<wbr></wbr>com/ubuntu/<wbr></wbr>dists/breezy/<wbr></wbr>main/installer-<wbr></wbr>powerpc/<wbr></wbr>current/<wbr></wbr>images/<wbr></wbr>MD5SUMS</a><br />
195- <a rel="nofollow" href="irc://irc.freenode.net/#launchpad">irc://irc.<wbr></wbr>freenode.<wbr></wbr>net/#launchpad</a></p>
196- <p>I have a Jabber account (<a rel="nofollow" href="jabber:foo@jabber.example.com">jabber:<wbr></wbr>foo@jabber.<wbr></wbr>example.<wbr></wbr>com</a>)<br />
197- Foo Bar &lt;<a rel="nofollow" href="mailto:foo.bar@example.net">mailto:<wbr></wbr>foo.bar@<wbr></wbr>example.<wbr></wbr>net</a>&gt;</p>
198+ <p><a rel="nofollow" href="https://launchpad.net/">https:/<wbr />/launchpad.<wbr />net/</a> is the new Launchpad site<br />
199+ <a rel="nofollow" href="http://example.com/something?foo=bar&amp;hum=baz">http://<wbr />example.<wbr />com/something?<wbr />foo=bar&amp;<wbr />hum=baz</a><br />
200+ You can check the PPC md5sums at <a rel="nofollow" href="ftp://ftp.ubuntu.com/ubuntu/dists/breezy/main/installer-powerpc/current/images/MD5SUMS">ftp://ftp.<wbr />ubuntu.<wbr />com/ubuntu/<wbr />dists/breezy/<wbr />main/installer-<wbr />powerpc/<wbr />current/<wbr />images/<wbr />MD5SUMS</a><br />
201+ <a rel="nofollow" href="irc://irc.freenode.net/#launchpad">irc://irc.<wbr />freenode.<wbr />net/#launchpad</a></p>
202+ <p>I have a Jabber account (<a rel="nofollow" href="jabber:foo@jabber.example.com">jabber:<wbr />foo@jabber.<wbr />example.<wbr />com</a>)<br />
203+ Foo Bar &lt;<a rel="nofollow" href="mailto:foo.bar@example.net">mailto:<wbr />foo.bar@<wbr />example.<wbr />net</a>&gt;</p>
204
205
206 URL linkification
207@@ -158,43 +158,43 @@
208 ... )
209
210 >>> print test_tales('foo/fmt:text-to-html', foo=text)
211- <p><a rel="nofollow" href="http://localhost:8086/bar/baz/foo.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>html</a><br />
212- <a rel="nofollow" href="ftp://localhost:8086/bar/baz/foo.bar.html">ftp://localhost<wbr></wbr>:8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a><br />
213- <a rel="nofollow" href="sftp://localhost:8086/bar/baz/foo.bar.html">sftp://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>.<br />
214- <a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>;<br />
215- <a rel="nofollow" href="news://localhost:8086/bar/baz/foo.bar.html">news://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>:<br />
216- <a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>?<br />
217- <a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>,<br />
218- &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>&gt;<br />
219- &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>&gt;,<br />
220- &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>&gt;.<br />
221- &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>&gt;;<br />
222- &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>&gt;:<br />
223- &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>&gt;?<br />
224- (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>)<br />
225- (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>),<br />
226- (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>).<br />
227- (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>);<br />
228- (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr></wbr>localhost:<wbr></wbr>8086/bar/<wbr></wbr>baz/foo.<wbr></wbr>bar.html</a>):<br />
229- <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&amp;b=a</a><br />
230- <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&amp;b=a</a>.<br />
231- <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&amp;b=a</a>,<br />
232- <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&amp;b=a</a>;<br />
233- <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&amp;b=a</a>:<br />
234- <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a:b;c@d_e%f~g#h,j!k-l+m$n*o'p">http://<wbr></wbr>localhost/<wbr></wbr>bar/baz/<wbr></wbr>foo.bar.<wbr></wbr>html?a=<wbr></wbr>b&amp;b=a:b;<wbr></wbr>c@d_e%f~<wbr></wbr>g#h,j!k-<wbr></wbr>l+m$n*o'<wbr></wbr>p</a><br />
235- <a rel="nofollow" href="http://www.searchtools.com/test/urls/(parens).html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/(parens)<wbr></wbr>.html</a><br />
236- <a rel="nofollow" href="http://www.searchtools.com/test/urls/-dash.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/-dash.<wbr></wbr>html</a><br />
237- <a rel="nofollow" href="http://www.searchtools.com/test/urls/_underscore.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/_underscor<wbr></wbr>e.html</a><br />
238- <a rel="nofollow" href="http://www.searchtools.com/test/urls/period.x.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/period.<wbr></wbr>x.html</a><br />
239- <a rel="nofollow" href="http://www.searchtools.com/test/urls/!exclamation.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/!exclamati<wbr></wbr>on.html</a><br />
240- <a rel="nofollow" href="http://www.searchtools.com/test/urls/~tilde.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/~tilde.<wbr></wbr>html</a><br />
241- <a rel="nofollow" href="http://www.searchtools.com/test/urls/*asterisk.html">http://<wbr></wbr>www.searchtools<wbr></wbr>.com/test/<wbr></wbr>urls/*asterisk.<wbr></wbr>html</a><br />
242- <a rel="nofollow" href="irc://irc.freenode.net/launchpad">irc://irc.<wbr></wbr>freenode.<wbr></wbr>net/launchpad</a><br />
243- <a rel="nofollow" href="irc://irc.freenode.net/%23launchpad,isserver">irc://irc.<wbr></wbr>freenode.<wbr></wbr>net/%23launchpa<wbr></wbr>d,isserver</a><br />
244- <a rel="nofollow" href="mailto:noreply@launchpad.net">mailto:<wbr></wbr>noreply@<wbr></wbr>launchpad.<wbr></wbr>net</a><br />
245- <a rel="nofollow" href="jabber:noreply@launchpad.net">jabber:<wbr></wbr>noreply@<wbr></wbr>launchpad.<wbr></wbr>net</a><br />
246- <a rel="nofollow" href="http://localhost/foo?xxx&amp;">http://<wbr></wbr>localhost/<wbr></wbr>foo?xxx&amp;</a><br />
247- <a rel="nofollow" href="http://localhost?testing=[square-brackets-in-query]">http://<wbr></wbr>localhost?<wbr></wbr>testing=<wbr></wbr>[square-<wbr></wbr>brackets-<wbr></wbr>in-query]</a></p>
248+ <p><a rel="nofollow" href="http://localhost:8086/bar/baz/foo.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />html</a><br />
249+ <a rel="nofollow" href="ftp://localhost:8086/bar/baz/foo.bar.html">ftp://localhost<wbr />:8086/bar/<wbr />baz/foo.<wbr />bar.html</a><br />
250+ <a rel="nofollow" href="sftp://localhost:8086/bar/baz/foo.bar.html">sftp://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>.<br />
251+ <a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>;<br />
252+ <a rel="nofollow" href="news://localhost:8086/bar/baz/foo.bar.html">news://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>:<br />
253+ <a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>?<br />
254+ <a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>,<br />
255+ &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>&gt;<br />
256+ &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>&gt;,<br />
257+ &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>&gt;.<br />
258+ &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>&gt;;<br />
259+ &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>&gt;:<br />
260+ &lt;<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>&gt;?<br />
261+ (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>)<br />
262+ (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>),<br />
263+ (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>).<br />
264+ (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>);<br />
265+ (<a rel="nofollow" href="http://localhost:8086/bar/baz/foo.bar.html">http://<wbr />localhost:<wbr />8086/bar/<wbr />baz/foo.<wbr />bar.html</a>):<br />
266+ <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr />localhost/<wbr />bar/baz/<wbr />foo.bar.<wbr />html?a=<wbr />b&amp;b=a</a><br />
267+ <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr />localhost/<wbr />bar/baz/<wbr />foo.bar.<wbr />html?a=<wbr />b&amp;b=a</a>.<br />
268+ <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr />localhost/<wbr />bar/baz/<wbr />foo.bar.<wbr />html?a=<wbr />b&amp;b=a</a>,<br />
269+ <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr />localhost/<wbr />bar/baz/<wbr />foo.bar.<wbr />html?a=<wbr />b&amp;b=a</a>;<br />
270+ <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a">http://<wbr />localhost/<wbr />bar/baz/<wbr />foo.bar.<wbr />html?a=<wbr />b&amp;b=a</a>:<br />
271+ <a rel="nofollow" href="http://localhost/bar/baz/foo.bar.html?a=b&amp;b=a:b;c@d_e%f~g#h,j!k-l+m$n*o'p">http://<wbr />localhost/<wbr />bar/baz/<wbr />foo.bar.<wbr />html?a=<wbr />b&amp;b=a:b;<wbr />c@d_e%f~<wbr />g#h,j!k-<wbr />l+m$n*o'<wbr />p</a><br />
272+ <a rel="nofollow" href="http://www.searchtools.com/test/urls/(parens).html">http://<wbr />www.searchtools<wbr />.com/test/<wbr />urls/(parens)<wbr />.html</a><br />
273+ <a rel="nofollow" href="http://www.searchtools.com/test/urls/-dash.html">http://<wbr />www.searchtools<wbr />.com/test/<wbr />urls/-dash.<wbr />html</a><br />
274+ <a rel="nofollow" href="http://www.searchtools.com/test/urls/_underscore.html">http://<wbr />www.searchtools<wbr />.com/test/<wbr />urls/_underscor<wbr />e.html</a><br />
275+ <a rel="nofollow" href="http://www.searchtools.com/test/urls/period.x.html">http://<wbr />www.searchtools<wbr />.com/test/<wbr />urls/period.<wbr />x.html</a><br />
276+ <a rel="nofollow" href="http://www.searchtools.com/test/urls/!exclamation.html">http://<wbr />www.searchtools<wbr />.com/test/<wbr />urls/!exclamati<wbr />on.html</a><br />
277+ <a rel="nofollow" href="http://www.searchtools.com/test/urls/~tilde.html">http://<wbr />www.searchtools<wbr />.com/test/<wbr />urls/~tilde.<wbr />html</a><br />
278+ <a rel="nofollow" href="http://www.searchtools.com/test/urls/*asterisk.html">http://<wbr />www.searchtools<wbr />.com/test/<wbr />urls/*asterisk.<wbr />html</a><br />
279+ <a rel="nofollow" href="irc://irc.freenode.net/launchpad">irc://irc.<wbr />freenode.<wbr />net/launchpad</a><br />
280+ <a rel="nofollow" href="irc://irc.freenode.net/%23launchpad,isserver">irc://irc.<wbr />freenode.<wbr />net/%23launchpa<wbr />d,isserver</a><br />
281+ <a rel="nofollow" href="mailto:noreply@launchpad.net">mailto:<wbr />noreply@<wbr />launchpad.<wbr />net</a><br />
282+ <a rel="nofollow" href="jabber:noreply@launchpad.net">jabber:<wbr />noreply@<wbr />launchpad.<wbr />net</a><br />
283+ <a rel="nofollow" href="http://localhost/foo?xxx&amp;">http://<wbr />localhost/<wbr />foo?xxx&amp;</a><br />
284+ <a rel="nofollow" href="http://localhost?testing=[square-brackets-in-query]">http://<wbr />localhost?<wbr />testing=<wbr />[square-<wbr />brackets-<wbr />in-query]</a></p>
285
286
287 The fmt:text-to-html formatter leaves a number of non-URIs unlinked:
288@@ -203,7 +203,7 @@
289 ... 'nothttp://launchpad.net/\n'
290 ... 'http::No-cache=True\n')
291 >>> print test_tales('foo/fmt:text-to-html', foo=text)
292- <p>nothttp:<wbr></wbr>//launchpad.<wbr></wbr>net/<br />
293+ <p>nothttp:<wbr />//launchpad.<wbr />net/<br />
294 http::No-cache=True</p>
295
296
297
298=== modified file 'lib/lp/app/doc/tales-email-formatting.txt'
299--- lib/lp/app/doc/tales-email-formatting.txt 2011-06-23 13:10:40 +0000
300+++ lib/lp/app/doc/tales-email-formatting.txt 2012-04-02 11:42:25 +0000
301@@ -122,8 +122,8 @@
302 ... '\n')
303 >>> print test_tales('foo/fmt:email-to-html',
304 ... foo='\n'.join([python, not_python]))
305- <p>&gt;&gt;&gt; tz = pytz.timezone(<wbr></wbr>"Asia/Calcutta"...
306- &gt;&gt;&gt; mydate = datetime.<wbr></wbr>datetime(<wbr></wbr>2007, 2, ...
307+ <p>&gt;&gt;&gt; tz = pytz.timezone(<wbr />"Asia/Calcutta"...
308+ &gt;&gt;&gt; mydate = datetime.<wbr />datetime(<wbr />2007, 2, ...
309 2007-02-18 15:35:00+05:30</p>
310 <p><span class="foldable-quoted">&gt; This line really is a quoted ...
311 &gt;&gt;&gt; This does not invoke an exception rule.
312@@ -154,11 +154,11 @@
313 ... '\n')
314 >>> print test_tales('foo/fmt:email-to-html', foo=dpkg)
315 <p>dpkg -l libdvdread3<br />
316- Desired=<wbr></wbr>Unknown/<wbr></wbr>Install/<wbr></wbr>...
317- | Status=<wbr></wbr>Not/Installed/<wbr></wbr>Config-<wbr></wbr>...
318- |/ Err?=(none)<wbr></wbr>/Hold/Reinst-<wbr></wbr>required/...
319+ Desired=<wbr />Unknown/<wbr />Install/<wbr />...
320+ | Status=<wbr />Not/Installed/<wbr />Config-<wbr />...
321+ |/ Err?=(none)<wbr />/Hold/Reinst-<wbr />required/...
322 ||/ Name Version Description<br />
323- +++-===<wbr></wbr>=======<wbr></wbr>====-==<wbr></wbr>=======...
324+ +++-===<wbr />=======<wbr />====-==<wbr />=======...
325 ii libdvdread3 0.9.7-2ubuntu1 library for reading DVDs</p>
326
327 >>> bad_dpkg = ('When dpkg output is in text, possibly tampered with,\n'
328@@ -175,11 +175,11 @@
329 ... foo='\n'.join([bad_dpkg]))
330 <p>When dpkg output is in text, possibly tampered with,<br />
331 we must take care to identify '|' quoted passages.<br />
332- $ Desired=<wbr></wbr>Unknown/<wbr></wbr>Install/<wbr></wbr>Remove/...
333+ $ Desired=<wbr />Unknown/<wbr />Install/<wbr />Remove/...
334 |<br />
335- &nbsp;Status=<wbr></wbr>Not/Installed/<wbr></wbr>Config-...
336- |/ Err?=(none)<wbr></wbr>/Hold/Reinst-<wbr></wbr>required/...
337+ &nbsp;Status=<wbr />Not/Installed/<wbr />Config-...
338+ |/ Err?=(none)<wbr />/Hold/Reinst-<wbr />required/...
339 ||/ Name Version Description<br />
340- +++-===<wbr></wbr>=======<wbr></wbr>====-==<wbr></wbr>=======...
341+ +++-===<wbr />=======<wbr />====-==<wbr />=======...
342 ii libdvdread3 0.9.7-2ubuntu1 library for reading DVDs</p>
343
344
345=== modified file 'lib/lp/app/doc/tales.txt'
346--- lib/lp/app/doc/tales.txt 2011-12-24 17:49:30 +0000
347+++ lib/lp/app/doc/tales.txt 2012-04-02 11:42:25 +0000
348@@ -287,7 +287,7 @@
349
350 >>> test_tales('foo/fmt:break-long-words',
351 ... foo='<http://launchpad.net/products/launchpad>')
352- '&lt;http:/<wbr></wbr>/launchpad.<wbr></wbr>...<wbr></wbr>launchpad&gt;'
353+ '&lt;http:/<wbr />/launchpad.<wbr />...<wbr />launchpad&gt;'
354
355 To get a int with its thousands separated by a comma, use fmt:intcomma.
356
357@@ -924,8 +924,8 @@
358 Version: GnuPG v1.4.1 (GNU/Linux)<br />
359 Comment: Using GnuPG with Thunderbird<br />
360 <br />
361- iD8DBQFED60Y0F+<wbr></wbr>nu1YWqI0RAqrNAJ<wbr></wbr>...
362- T2PIWy0CUJsX8RX<wbr></wbr>St/M51WE=<br />
363+ iD8DBQFED60Y0F+<wbr />nu1YWqI0RAqrNAJ<wbr />...
364+ T2PIWy0CUJsX8RX<wbr />St/M51WE=<br />
365 =J2S5<br />
366 -----END PGP SIGNATURE-----
367 </span></p>
368@@ -942,7 +942,7 @@
369 Raise your hand if you can read this.</p>
370 <p><span class="foldable"...>--<br />
371 __C U R T I S C. H O V E Y_______<br />
372- sinzui.<wbr></wbr>is@example.<wbr></wbr>org<br />
373+ sinzui.<wbr />is@example.<wbr />org<br />
374 Guilty of stealing everything I am.
375 </span></p>
376
377@@ -1012,7 +1012,7 @@
378 </span></p>
379 <p><span class="foldable"...>--<br />
380 __C U R T I S C. H O V E Y_______<br />
381- sinzui.<wbr></wbr>is@example.<wbr></wbr>org<br />
382+ sinzui.<wbr />is@example.<wbr />org<br />
383 Guilty of stealing everything I am.
384 </span></p>
385
386
387=== modified file 'lib/lp/blueprints/workitemmigration.py'
388--- lib/lp/blueprints/workitemmigration.py 2012-03-22 23:21:24 +0000
389+++ lib/lp/blueprints/workitemmigration.py 2012-04-02 11:42:25 +0000
390@@ -75,7 +75,7 @@
391
392 def milestone_extract(text, valid_milestones):
393 words = text.replace('(', ' ').replace(')', ' ').replace(
394- '[', ' ').replace(']', ' ').replace('<wbr></wbr>', '').split()
395+ '[', ' ').replace(']', ' ').replace('<wbr />', '').split()
396 for milestone in valid_milestones:
397 for word in words:
398 if word == milestone.name:
399
400=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt'
401--- lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt 2012-02-21 22:30:41 +0000
402+++ lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt 2012-04-02 11:42:25 +0000
403@@ -191,8 +191,8 @@
404 Version: GnuPG v1.4.1 (GNU/Linux)<br />
405 Comment: Using GnuPG with Thunderbird<br />
406 <br />
407- iD8DBQFED60Y0F+<wbr></wbr>nu1YWqI0RAqrNAJ<wbr></wbr>...
408- T2PIWy0CUJsX8RX<wbr></wbr>St/M51WE=<br />
409+ iD8DBQFED60Y0F+<wbr />nu1YWqI0RAqrNAJ<wbr />...
410+ T2PIWy0CUJsX8RX<wbr />St/M51WE=<br />
411 =J2S5<br />
412 -----END PGP SIGNATURE-----
413 </span></p>
414
415=== modified file 'lib/lp/registry/browser/tests/person-views.txt'
416--- lib/lp/registry/browser/tests/person-views.txt 2012-02-10 19:52:27 +0000
417+++ lib/lp/registry/browser/tests/person-views.txt 2012-04-02 11:42:25 +0000
418@@ -28,7 +28,7 @@
419 >>> print view.homepage_content
420 <p>line one &lt;script&gt;</p>
421 <BLANKLINE>
422- <p><a rel="nofollow" href="http://aa.aa/">http://<wbr></wbr>aa.aa/</a></p>
423+ <p><a rel="nofollow" href="http://aa.aa/">http://<wbr />aa.aa/</a></p>
424
425 Teams are always valid and do not have probation rules; the homepage
426 content is formatted HTML.
427@@ -44,7 +44,7 @@
428 >>> print view.homepage_content
429 <p>line one &lt;script&gt;</p>
430 <BLANKLINE>
431- <p><a rel="nofollow" href="http://aa.aa/">http://<wbr></wbr>aa.aa/</a></p>
432+ <p><a rel="nofollow" href="http://aa.aa/">http://<wbr />aa.aa/</a></p>
433
434 New users are on probation; the homepage content is escaped HTML.
435
436
437=== modified file 'lib/lp/registry/stories/announcements/xx-announcements.txt'
438--- lib/lp/registry/stories/announcements/xx-announcements.txt 2011-12-20 10:52:02 +0000
439+++ lib/lp/registry/stories/announcements/xx-announcements.txt 2012-04-02 11:42:25 +0000
440@@ -764,7 +764,7 @@
441 Newline="&lt;br /&gt;
442 "&lt;br /&gt;
443 url="&lt;a rel="nofollow"
444- href="http://www.ubuntu.com"&gt;http://&lt;wbr&gt;&lt;/wbr&gt;www.ubuntu.&lt;wbr&gt;&lt;/wbr&gt;com&lt;/a&gt;"...
445+ href="http://www.ubuntu.com"&gt;http://&lt;wbr /&gt;www.ubuntu.&lt;wbr /&gt;com&lt;/a&gt;"...
446
447
448 Deletion