--- a/src/reportlab/lib/utils.py Mon Mar 08 11:27:31 2010 +0000
+++ b/src/reportlab/lib/utils.py Mon Mar 08 13:01:55 2010 +0000
@@ -1129,3 +1129,31 @@
A.append(msg)
v.args = tuple(A)
raise t,v,b
+
+def escapeOnce(data):
+ """Ensure XML output is escaped just once, irrespective of input
+
+ >>> escapeOnce('A & B')
+ 'A & B'
+ >>> escapeOnce('C & D')
+ 'C & D'
+ >>> escapeOnce('E & F')
+ 'E & F'
+
+ """
+ data = data.replace("&", "&")
+
+ #...but if it was already escaped, make sure it
+ # is not done twice....this will turn any tags
+ # back to how they were at the start.
+ data = data.replace("&", "&")
+ data = data.replace(">", ">")
+ data = data.replace("&lt;", "<")
+ data = data.replace("&#", "&#")
+
+ #..and just in case someone had double-escaped it, do it again
+ data = data.replace("&amp;", "&")
+ data = data.replace("&gt;", ">")
+ data = data.replace("&lt;", "<")
+ return data
+
--- a/src/reportlab/platypus/tableofcontents.py Mon Mar 08 11:27:31 2010 +0000
+++ b/src/reportlab/platypus/tableofcontents.py Mon Mar 08 13:01:55 2010 +0000
@@ -46,7 +46,7 @@
from reportlab.lib import enums
from reportlab.lib.units import cm
-from reportlab.lib.utils import commasplit
+from reportlab.lib.utils import commasplit, escapeOnce
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus.paragraph import Paragraph
from reportlab.platypus.doctemplate import IndexingFlowable
@@ -446,7 +446,6 @@
alphaStyle = self.getLevelStyle(0)
for texts, pageNumbers in _tempEntries:
texts = list(texts)
-
#track when the first character changes; either output some extra
#space, or the first letter on a row of its own. We cannot do
#widow/orphan control, sadly.
@@ -469,6 +468,11 @@
label = encodestring(dumps(list(pageNumbers))).strip()
texts[-1] = '%s<onDraw name="drawIndexEntryEnd" label="%s"/>' % (texts[-1], label)
for text in texts:
+ #Platypus and RML differ on how parsed XML attributes are escaped.
+ #e.g. <index item="M&S"/>. The only place this seems to bite us is in
+ #the index entries so work around it here.
+ text = escapeOnce(text)
+
style = self.getLevelStyle(i+leveloffset)
para = Paragraph(text, style)
if style.spaceBefore:
--- a/tests/test_platypus_index.py Mon Mar 08 11:27:31 2010 +0000
+++ b/tests/test_platypus_index.py Mon Mar 08 13:01:55 2010 +0000
@@ -94,12 +94,21 @@
description = '<font color=red>%s</font>' % (self.test0.__doc__ % (headers and 'with alphabetic headers ' or ''))
story.append(Paragraph(description, bt))
index = SimpleIndex(dot=' . ', headers=headers)
+
for i in range(20):
words = randomtext.randomText(randomtext.PYTHON, 5).split(' ')
txt = ' '.join([(len(w) > 5 and '<index item=%s/>%s' % (quoteattr(commajoin([w[:2], w[:3], w])), w) or w) for w in words])
para = Paragraph(txt, makeBodyStyle())
story.append(para)
+
+
+ #test ampersand in index term
+ txt = '\nMarks & Spencer - purveyors of fine groceries, underwear and ampersands - should have their initials displayed however they were input.\n<index item="M&S,groceries"/><index item="M&S,underwear"/><index item="M&S,ampersands"/>'
+ para = Paragraph(txt, makeBodyStyle())
+ story.append(para)
+
+
story.append(index)
doc.build(story, canvasmaker=index.getCanvasMaker())