# HG changeset patch
# User Roger Haase <crosseyedpenguin@yahoo.com>
# Date 1282508807 25200
# Node ID 7dc33afe0996ff9f074b352e76431b2a20b30874
# Parent  7a83cc907f6855d1254a94ab731854b4554539a2
make RecentChanges page name and comment fields breakable with use of zero-width spaces and soft hyphen word breaks

diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/Page.py
--- a/MoinMoin/Page.py	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/Page.py	Sun Aug 22 13:26:47 2010 -0700
@@ -707,15 +707,14 @@
                 wikiutil.version2timestamp(t))
         return result
 
-    def split_title(self, force=0):
-        """ Return a string with the page name split by spaces, if the user wants that.
+    def split_title(self):
+        """ Return a string with the page name split by spaces per user preferences.
 
-        @param force: if != 0, then force splitting the page_name
         @rtype: unicode
-        @return: pagename of this page, splitted into space separated words
+        @return: pagename of this page, split into space separated words
         """
         request = self.request
-        if not force and not request.user.wikiname_add_spaces:
+        if not request.user.wikiname_add_spaces:
             return self.page_name
 
         # look for the end of words and the start of a new word,
@@ -773,7 +772,7 @@
                                  formatter=getattr(self, 'formatter', None), **kw)
         return link
 
-    def link_to(self, request, text=None, querystr=None, anchor=None, **kw):
+    def link_to(self, request, text=None, querystr=None, anchor=None, no_escape=None, **kw):
         """ Return HTML markup that links to this page.
 
         See wikiutil.link_tag() for possible keyword parameters.
@@ -790,7 +789,8 @@
         """
         if not text:
             text = self.split_title()
-        text = wikiutil.escape(text)
+        if not no_escape:
+            text = wikiutil.escape(text)
 
         # Add css class for non existing page
         if not self.exists():
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/_tests/test_Page.py
--- a/MoinMoin/_tests/test_Page.py	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/_tests/test_Page.py	Sun Aug 22 13:26:47 2010 -0700
@@ -40,10 +40,6 @@
         edit_info = page.edit_info()
         assert edit_info == {}
 
-    def testSplitTitle(self):
-        page = Page(self.request, u"FrontPage")
-        assert page.split_title(force=True) == u'Front Page'
-
     def testGetRevList(self):
         page = Page(self.request, u"FrontPage")
         assert 1 in page.getRevList()
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/macro/RecentChanges.py
--- a/MoinMoin/macro/RecentChanges.py	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/macro/RecentChanges.py	Sun Aug 22 13:26:47 2010 -0700
@@ -5,13 +5,15 @@
     Parameter "ddiffs" by Ralf Zosel <ralf@zosel.com>, 04.12.2003.
 
     @copyright: 2000-2004 Juergen Hermann <jh@web.de>
+    @copyright: 2010 Roger D. Haase, added zero-width spaces and soft hyphen word breaks
     @license: GNU GPL, see COPYING for details.
 """
-import time
+import time, re
 
 from MoinMoin import util, wikiutil
 from MoinMoin.Page import Page
 from MoinMoin.logfile import editlog
+from MoinMoin.util.chartypes import chars_lower, chars_upper
 
 _DAYS_SELECTION = [1, 2, 3, 7, 14, 30, 60, 90]
 _MAX_DAYS = 7
@@ -24,6 +26,36 @@
 
 Dependencies = ["time"] # ["user", "pages", "pageparams", "bookmark"]
 
+def split_page_name(page_name):
+    """ Return a string with the page name split by zero-width spaces.
+
+    @param page_name: the name of the page to be split
+    @rtype: unicode
+    @return: pagename of this page, split into space separated words
+    """
+    if len(page_name) <= _MAX_PAGENAME_LENGTH:
+        return page_name
+
+    zero_width_space = "&#8203;"
+    # treat "/" as a lower case character
+    split_regex = re.compile('([%s])([%s])' % ("/" + chars_lower, chars_upper), re.UNICODE)
+    split_by_spaces = page_name.split(" ")
+    space_parts = []
+    for split_by_space in split_by_spaces:
+        split_by_camelcases = split_regex.sub(r'\1 \2', split_by_space).split(" ")
+        camelcase_parts = []
+        for split_by_camelcase in split_by_camelcases:
+            if len(split_by_camelcase) > _MAX_PAGENAME_LENGTH:
+                lowercase_parts = []
+                while split_by_camelcase:
+                    lowercase_parts.append(wikiutil.escape(split_by_camelcase[:_MAX_PAGENAME_LENGTH]))
+                    split_by_camelcase = split_by_camelcase[_MAX_PAGENAME_LENGTH:]
+                camelcase_parts.append(zero_width_space.join(lowercase_parts))
+            else:
+                camelcase_parts.append(wikiutil.escape(split_by_camelcase))
+        space_parts.append(zero_width_space.join(camelcase_parts))
+    return " ".join(space_parts)
+
 def format_comment(request, line):
     comment = line.comment
     action = line.action
@@ -89,11 +121,8 @@
         img = request.theme.make_icon('diffrc')
         html_link = page.link_to_raw(request, img, querystr={'action': 'diff'}, rel='nofollow')
 
-    # print name of page, with a link to it
-    force_split = len(page.page_name) > _MAX_PAGENAME_LENGTH
-
     d['icon_html'] = html_link
-    d['pagelink_html'] = page.link_to(request, text=page.split_title(force=force_split))
+    d['pagelink_html'] = page.link_to(request, text=split_page_name(page.page_name), no_escape=1)
 
     # print time of change
     d['time_html'] = None
@@ -128,7 +157,7 @@
     for idx in range(len(lines)):
         comment = format_comment(request, lines[idx])
         if comment:
-            comments.append((idx+1, wikiutil.escape(comment)))
+            comments.append((idx+1, comment))
 
     d['changecount'] = len(lines)
     d['comments'] = comments
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/macro/_tests/test_recentchanges.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/macro/_tests/test_recentchanges.py	Sun Aug 22 13:26:47 2010 -0700
@@ -0,0 +1,45 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MoinMoin.macro.RecentChanges.py tests
+
+    @copyright: 2010 by Roger D. Haase
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import py
+
+from MoinMoin.macro.RecentChanges import split_page_name, _MAX_PAGENAME_LENGTH, _MAX_COMMENT_LENGTH
+# make_breakable is only used in RecentChanges macro, so test it here
+from MoinMoin.wikiutil import make_breakable
+
+shy = "&shy;" # soft_hyphen
+zws = "&#8203;" # zero_width_space
+
+class Test_Split_Page_Name:
+
+    def test_split_page_name(self):
+        assert _MAX_PAGENAME_LENGTH == 15
+        name = 'MyPageName'
+        assert split_page_name(name) == name
+        name = 'My Page Name'
+        assert split_page_name(name) == name
+        name = 'Page Name Over Fifteen Characters'
+        assert split_page_name(name) == name
+        name = 'Page%sName%sOver%sFifteen%sCharacters'
+        assert split_page_name(name % ("", "", "", "")) == name % (zws, zws, zws, zws)
+        name = 'pagenamewithnoc%samelcase'
+        assert split_page_name(name % ("")) == name % (zws)
+        name = 'pagenamewithcam%smelcase%sStarting%sAfter%sPosition15'
+        assert split_page_name(name % ("", "", "", "")) == name % (zws, zws, zws, zws)
+
+class Test_Split_Comment:
+
+    def test_split_comment(self):
+        maxlen = _MAX_COMMENT_LENGTH
+        assert _MAX_COMMENT_LENGTH == 20
+        comment = 'my comment'
+        assert make_breakable(comment, maxlen) == comment
+        comment = 'my comment that has more than twenty characters'
+        assert make_breakable(comment, maxlen) == comment
+        comment = 'mycommentthathasmore%sthantwentycharacters%swithoutanyspaces'
+        assert make_breakable(comment % ("", ""), maxlen) == comment % (shy, shy)
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/web/static/htdocs/classic/css/msie.css
--- a/MoinMoin/web/static/htdocs/classic/css/msie.css	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/web/static/htdocs/classic/css/msie.css	Sun Aug 22 13:26:47 2010 -0700
@@ -35,3 +35,7 @@
  */
 * html div#page, * html div#header { height: 0.001%; }
 
+/* IE6 support for zero width spaces on RecentChanges page */
+td.rcpagelink {
+	font-family:  "Lucida Sans Unicode", "Arial Unicode MS", "Arial Unicode";
+}
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/web/static/htdocs/modern/css/msie.css
--- a/MoinMoin/web/static/htdocs/modern/css/msie.css	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/web/static/htdocs/modern/css/msie.css	Sun Aug 22 13:26:47 2010 -0700
@@ -35,3 +35,7 @@
  */
 * html div#page, * html div#header { height: 0.001%; }
 
+/* IE6 support for zero width spaces on RecentChanges page */
+td.rcpagelink {
+	font-family:  "Lucida Sans Unicode", "Arial Unicode MS", "Arial Unicode";
+}
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/web/static/htdocs/modernized/css/msie.css
--- a/MoinMoin/web/static/htdocs/modernized/css/msie.css	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/web/static/htdocs/modernized/css/msie.css	Sun Aug 22 13:26:47 2010 -0700
@@ -35,3 +35,7 @@
  */
 * html div#page, * html div#header { height: 0.001%; }
 
+/* IE6 support for zero width spaces on RecentChanges page */
+td.rcpagelink {
+	font-family:  "Lucida Sans Unicode", "Arial Unicode MS", "Arial Unicode";
+}
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/web/static/htdocs/rightsidebar/css/msie.css
--- a/MoinMoin/web/static/htdocs/rightsidebar/css/msie.css	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/web/static/htdocs/rightsidebar/css/msie.css	Sun Aug 22 13:26:47 2010 -0700
@@ -38,3 +38,7 @@
  */
 * html div#page, * html div#header { height: 0.001%; }
 
+/* IE6 support for zero width spaces on RecentChanges page */
+td.rcpagelink {
+	font-family:  "Lucida Sans Unicode", "Arial Unicode MS", "Arial Unicode";
+}
diff -r 7a83cc907f68 -r 7dc33afe0996 MoinMoin/wikiutil.py
--- a/MoinMoin/wikiutil.py	Fri Jul 23 09:23:59 2010 -0700
+++ b/MoinMoin/wikiutil.py	Sun Aug 22 13:26:47 2010 -0700
@@ -201,17 +201,21 @@
 
 
 def make_breakable(text, maxlen):
-    """ make a text breakable by inserting spaces into nonbreakable parts
+    """ make a text breakable by inserting soft hyphens into nonbreakable parts and escaping special chars
     """
+    soft_hyphen = "&shy;"
     text = text.split(" ")
     newtext = []
-    for part in text:
-        if len(part) > maxlen:
-            while part:
-                newtext.append(part[:maxlen])
-                part = part[maxlen:]
+    for word in text:
+        if len(word) > maxlen:
+            part = []
+            while word:
+                part.append(escape(word[:maxlen]))
+                word = word[maxlen:]
+            newtext.append(soft_hyphen.join(part))
         else:
-            newtext.append(part)
+            word = escape(word)
+            newtext.append(word)
     return " ".join(newtext)
 
 ########################################################################
