Index: trac/ticket/web_ui.py
===================================================================
--- trac/ticket/web_ui.py	(revision 2763)
+++ trac/ticket/web_ui.py	(working copy)
@@ -240,7 +240,7 @@
                 yield ('ticket_details', 'Ticket details', False)
 
     def get_timeline_events(self, req, start, stop, filters):
-        rss = req.args.get('format') == 'rss' # Kludge
+        format = req.args.get('format')
 
         status_map = {'new': ('newticket', 'created'),
                       'reopened': ('newticket', 'reopened'),
@@ -268,15 +268,15 @@
             kind, verb = status_map[status]
             title = util.Markup('Ticket <em title="%s">#%s</em> (%s) %s by %s',
                                 summary, id, type, verb, author)
-            href = rss and self.env.abs_href.ticket(id) \
-                   or self.env.href.ticket(id)
+            href = format == 'rss' and self.env.abs_href.ticket(id) or \
+                   self.env.href.ticket(id)
 
             if status == 'new':
-                message = util.escape(summary)
+                message = summary
             else:
                 message = util.Markup(info)
                 if comment:
-                    if rss:
+                    if format == 'rss':
                         message += wiki_to_html(comment, self.env, req, db,
                                                 absurls=True)
                     else:
Index: trac/ticket/report.py
===================================================================
--- trac/ticket/report.py	(revision 2763)
+++ trac/ticket/report.py	(working copy)
@@ -1,7 +1,8 @@
 # -*- coding: iso-8859-1 -*-
 #
-# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2006 Edgewall Software
 # Copyright (C) 2003-2004 Jonas Borgström <jonas@edgewall.com>
+# Copyright (C) 2006 Christian Boos <cboos@neuf.fr>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -254,7 +255,8 @@
 
         title, description, sql = self.get_info(db, id, args)
 
-        if req.args.get('format') == 'sql':
+        format = req.args.get('format')
+        if format == 'sql':
             self._render_sql(req, id, title, description, sql)
             return
 
@@ -344,7 +346,9 @@
                         id_val = row[id_cols[0]]
                         value['ticket_href'] = self.env.href.ticket(id_val)
                 elif column == 'description':
-                    value['parsed'] = wiki_to_html(cell, self.env, req, db)
+                    descr = wiki_to_html(cell, self.env, req, db,
+                                         absurls=(format == 'rss'))
+                    value['parsed'] = format == 'rss' and str(descr) or descr
                 elif column == 'reporter' and cell.find('@') != -1:
                     value['rss'] = cell
                 elif column == 'report':
@@ -363,9 +367,7 @@
             row_idx += 1
         req.hdf['report.numrows'] = row_idx
 
-        format = req.args.get('format')
         if format == 'rss':
-            self._render_rss(req)
             return 'report_rss.cs', 'application/rss+xml'
         elif format == 'csv':
             self._render_csv(req, cols, rows)
@@ -480,18 +482,6 @@
                                        .replace('\r',' ')
             req.write(sep.join(map(sanitize, row)) + '\r\n')
 
-    def _render_rss(self, req):
-        # Escape HTML in the ticket summaries
-        item = req.hdf.getObj('report.items')
-        if item:
-            item = item.child()
-            while item:
-                for col in ('summary', 'description.parsed'):
-                    nodename = 'report.items.%s.%s' % (item.name(), col)
-                    value = req.hdf.get(nodename, '')
-                    req.hdf[nodename] = value
-                item = item.next()
-
     def _render_sql(self, req, id, title, description, sql):
         req.perm.assert_permission('REPORT_SQL_VIEW')
         req.send_response(200)
Index: trac/ticket/roadmap.py
===================================================================
--- trac/ticket/roadmap.py	(revision 2763)
+++ trac/ticket/roadmap.py	(working copy)
@@ -316,13 +316,13 @@
                 title = Markup('Milestone <em>%s</em> completed', name)
                 if format == 'rss':
                     href = self.env.abs_href.milestone(name)
-                    message = wiki_to_html(description or '--', self.env,
-                                           req, db, absurls=True)
+                    message = wiki_to_html(description, self.env, db,
+                                           absurls=True)
                 else:
                     href = self.env.href.milestone(name)
                     message = wiki_to_oneliner(description, self.env, db,
                                                shorten=True)
-                yield 'milestone', href, title, completed, None, message
+                yield 'milestone', href, title, completed, None, message or '--'
 
     # IRequestHandler methods
 
Index: trac/ticket/query.py
===================================================================
--- trac/ticket/query.py	(revision 2763)
+++ trac/ticket/query.py	(working copy)
@@ -1,7 +1,8 @@
 # -*- coding: iso-8859-1 -*-
 #
-# Copyright (C) 2004-2005 Edgewall Software
+# Copyright (C) 2004-2006 Edgewall Software
 # Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2005-2006 Christian Boos <cboos@neuf.fr>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -592,9 +593,9 @@
                 result['reporter'] = ''
             if result['description']:
                 # str() cancels out the Markup() returned by wiki_to_html
-                result['description'] = str(wiki_to_html(result['description'] or '',
-                                                         self.env, req, db,
-                                                         absurls=1))
+                descr = wiki_to_html(result['description'], self.env, req, db,
+                                     absurls=True)
+                result['description'] = str(descr)
             if result['time']:
                 result['time'] = http_date(result['time'])
         req.hdf['query.results'] = results
Index: trac/versioncontrol/web_ui/util.py
===================================================================
--- trac/versioncontrol/web_ui/util.py	(revision 2763)
+++ trac/versioncontrol/web_ui/util.py	(working copy)
@@ -1,7 +1,8 @@
 # -*- coding: iso-8859-1 -*-
 #
-# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2006 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
+# Copyright (C) 2005-2006 Christian Boos <cboos@neuf.fr>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -13,12 +14,13 @@
 # history and logs, available at http://projects.edgewall.com/trac/.
 #
 # Author: Jonas Borgström <jonas@edgewall.com>
+#         Christian Boos <cboos@neuf.fr>
 
 import re
 import urllib
 
 from trac.util import escape, format_datetime, pretty_timedelta, shorten_line, \
-                      TracError, Markup
+                      TracError, Markup, rss_title
 from trac.wiki import wiki_to_html, wiki_to_oneliner
 
 __all__ = ['get_changes', 'get_path_links', 'get_path_rev_line',
@@ -30,26 +32,27 @@
     for rev in revs:
         changeset = repos.get_changeset(rev)
         message = changeset.message or '--'
-        files = None
+        shortlog = wiki_to_oneliner(message, env, db, shorten=True)
         if format == 'changelog':
             files = [change[0] for change in changeset.get_changes()]
-        elif message:
-            if not full:
-                message = wiki_to_oneliner(message, env, db,
-                                           shorten=True)
-            else:
+        else:
+            files = None
+            if full:
                 message = wiki_to_html(message, env, req, db,
                                        absurls=(format == 'rss'),
                                        escape_newlines=True)
-        if not message:
-            message = '--'
+            else:
+                message = shortlog
+            if format == 'rss':
+                shortlog = rss_title(shortlog)
+                message = str(message)
         changes[rev] = {
             'date_seconds': changeset.date,
             'date': format_datetime(changeset.date),
             'age': pretty_timedelta(changeset.date),
             'author': changeset.author or 'anonymous',
             'message': message,
-            'shortlog': shorten_line(message),
+            'shortlog': shortlog,
             'files': files
         }
     return changes
Index: trac/versioncontrol/web_ui/changeset.py
===================================================================
--- trac/versioncontrol/web_ui/changeset.py	(revision 2763)
+++ trac/versioncontrol/web_ui/changeset.py	(working copy)
@@ -1,8 +1,9 @@
 # -*- coding: iso-8859-1 -*-
 #
-# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2006 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
 # Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2005-2006 Christian Boos <cboos@neuf.fr>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -113,9 +114,10 @@
             repos = self.env.get_repository()
             for chgset in repos.get_changesets(start, stop):
                 message = chgset.message or '--'
+                shortlog = wiki_to_oneliner(message, self.env, db, shorten=True)
                 if format == 'rss':
-                    title = util.Markup('Changeset <em>[%s]</em>: %s',
-                                        chgset.rev, util.shorten_line(message))
+                    title = util.Markup('Changeset [%s]: %s',
+                                        chgset.rev, shortlog)
                     href = self.env.abs_href.changeset(chgset.rev)
                     message = wiki_to_html(message, self.env, req, db,
                                            absurls=True)
@@ -129,14 +131,17 @@
                     files = []
                     for chg in chgset.get_changes():
                         if show_files > 0 and len(files) >= show_files:
-                            files.append('...')
+                            files.append('&hellip;')
                             break
-                        files.append('<span class="%s">%s</span>'
-                                     % (chg[2], util.escape(chg[0])))
-                    message = '<span class="changes">' + ', '.join(files) +\
-                              '</span>: ' + message
+                        print chg
+                        files.append(util.Markup('<span class="%s">%s</span>',
+                                                 chg[2], chg[0] or '/'))
+                    message = util.Markup('<span class="changes">%s</span> %s',
+                                          util.Markup('<br /> '.join(files)),
+                                          message)
+                    print message
                 yield 'changeset', href, title, chgset.date, chgset.author,\
-                      util.Markup(message)
+                      message
 
     # Internal methods
 
Index: trac/versioncontrol/web_ui/log.py
===================================================================
--- trac/versioncontrol/web_ui/log.py	(revision 2763)
+++ trac/versioncontrol/web_ui/log.py	(working copy)
@@ -1,7 +1,8 @@
 # -*- coding: iso-8859-1 -*-
 #
-# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2006 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
+# Copyright (C) 2005-2006 Christian Boos <cboos@neuf.fr>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -157,7 +158,6 @@
                 if email:
                     email_map[username] = email
             for cs in changes.values():
-                cs['shortlog'] = cs['shortlog'].replace('\n', ' ')
                 # For RSS, author must be an email address
                 author = cs['author']
                 author_email = ''
Index: trac/Timeline.py
===================================================================
--- trac/Timeline.py	(revision 2763)
+++ trac/Timeline.py	(working copy)
@@ -1,8 +1,9 @@
 # -*- coding: iso-8859-1 -*-
 #
-# Copyright (C) 2003-2005 Edgewall Software
+# Copyright (C) 2003-2006 Edgewall Software
 # Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
 # Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2005-2006 Christian Boos <cboos@neuf.fr>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -21,7 +22,7 @@
 
 from trac.core import *
 from trac.perm import IPermissionRequestor
-from trac.util import format_date, format_time, http_date, Markup
+from trac.util import format_date, format_time, http_date, Markup, rss_title
 from trac.web import IRequestHandler
 from trac.web.chrome import add_link, add_stylesheet, INavigationContributor
 
@@ -158,10 +159,8 @@
 
             if format == 'rss':
                 # Strip/escape HTML markup
-                if isinstance(title, Markup):
-                    event['title'] = title.striptags()
-                else:
-                    event['title'] = title
+                event['title'] = rss_title(title)
+                event['message'] = str(message)
 
                 if author:
                     # For RSS, author must be an email address
Index: trac/util.py
===================================================================
--- trac/util.py	(revision 2763)
+++ trac/util.py	(working copy)
@@ -246,6 +246,19 @@
         return text
     return text.unescape()
 
+ENTITIES = re.compile(r"&(?:(\w+)|#(\d+));")
+def rss_title(text):
+    if isinstance(text, Markup):
+        def replace_entity(match):
+            named = match.group(1)
+            num = match.group(2)
+            return num and int(num) < 128 and match.group(0) or \
+                   named in ('gt', 'lt', 'amp') and named or ''
+        return re.sub(ENTITIES, replace_entity, text.striptags())
+    return text
+
+
+
 def to_utf8(text, charset='iso-8859-15'):
     """Convert a string to UTF-8, assuming the encoding is either UTF-8, ISO
     Latin-1, or as specified by the optional `charset` parameter."""

