Index display code now handles 'M&S'-like attributes however they are escaped
authorandy
Mon, 08 Mar 2010 13:01:55 +0000
changeset 3349 b67514b01536
parent 3348 d44de6313b58
child 3350 dd8dc4cf3785
Index display code now handles 'M&S'-like attributes however they are escaped
src/reportlab/lib/utils.py
src/reportlab/platypus/tableofcontents.py
tests/test_platypus_index.py
--- 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("<", "<")
+    data = data.replace("&#", "&#")
+
+    #..and just in case someone had double-escaped it, do it again
+    data = data.replace("&", "&")
+    data = data.replace(">", ">")
+    data = data.replace("<", "<")
+    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 &amp; 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&amp;S,ampersands"/>'
+            para = Paragraph(txt, makeBodyStyle())
+            story.append(para)
+        
+
             story.append(index)
     
             doc.build(story, canvasmaker=index.getCanvasMaker())