Changeset 89:757b097c61a5
- Timestamp:
- 01/15/09 07:38:25 (19 months ago)
- Branch:
- default
- Location:
- relatorio
- Files:
-
- 3 modified
-
templates/opendocument.py (modified) (13 diffs)
-
tests/test_api.py (modified) (4 diffs)
-
tests/test_odt.py (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
relatorio/templates/opendocument.py
r84 r89 11 11 # This program is distributed in the hope that it will be useful, but WITHOUT 12 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 14 # details. 15 15 # … … 42 42 ChartTemplate = type(None) 43 43 44 GENSHI_EXPR = re.compile(r'''((/)?(for|choose|otherwise|when|if|with)\s*(\s(\w+)=["'](.*)["']|$)|.*)''') 44 GENSHI_EXPR = re.compile(r''' 45 (/)? # is this a closing tag? 46 (for|choose|otherwise|when|if|with) # tag directive 47 \s* 48 (?:\s(\w+)=["'](.*)["']|$) # match a single attr & its value 49 | 50 .* # or anything else 51 ''', re.VERBOSE) 52 45 53 EXTENSIONS = {'image/png': 'png', 46 54 'image/jpeg': 'jpg', … … 65 73 66 74 class ImageHref: 67 "A class used to add image in the odf zipfile"68 75 "A class used to add images in the odf zipfile" 76 69 77 def __init__(self, zfile, context): 70 78 self.zip = zfile … … 72 80 73 81 def __call__(self, expr, name): 82 #FIXME: name argument is unused 74 83 bitstream, mimetype = expr 75 84 if isinstance(bitstream, Report): … … 97 106 def _parse(self, source, encoding): 98 107 """parses the odf file. 99 108 100 109 It adds genshi directives and finds the inner docs. 101 110 """ … … 115 124 content = zf.read(c_path) 116 125 styles = zf.read(s_path) 117 126 118 127 c_parsed = template._parse(self.insert_directives(zf.read(c_path)), 119 128 encoding) … … 147 156 def _invert_style(self, tree): 148 157 "inverts the text:a and text:span" 149 xpath_expr = "//text:a[starts-with(@xlink:href, 'relatorio://')]" \158 xpath_expr = "//text:a[starts-with(@xlink:href, 'relatorio://')]" \ 150 159 "/text:span" 151 160 for span in tree.xpath(xpath_expr, namespaces=self.namespaces): … … 163 172 # If this node href matches a genshi directive it is kept for further 164 173 # processing. 165 r_statements, genshi_dir = [], []166 174 xlink_href_attrib = '{%s}href' % self.namespaces['xlink'] 167 175 text_a = '{%s}a' % self.namespaces['text'] 168 176 placeholder = '{%s}placeholder' % self.namespaces['text'] 169 170 177 s_xpath = "//text:a[starts-with(@xlink:href, 'relatorio://')]" \ 171 178 "| //text:placeholder" 179 180 r_statements = [] 181 opened_tags = [] 182 # We map each opening tag with its closing tag 183 closing_tags = {} 172 184 for statement in tree.xpath(s_xpath, namespaces=self.namespaces): 173 185 if statement.tag == placeholder: … … 182 194 warnings.warn('No statement text in %s' % self.filepath) 183 195 elif expr != statement.text and statement.tag == text_a: 184 warnings.warn('url and text do not match in %s: %s != %s' 196 warnings.warn('url and text do not match in %s: %s != %s' 185 197 % (self.filepath, expr, 186 198 statement.text.encode('utf-8'))) 187 199 188 expr, closing, directive, _, attr, attr_val = \200 closing, directive, attr, attr_val = \ 189 201 GENSHI_EXPR.match(expr).groups() 202 is_opening = closing != '/' 190 203 if directive is not None: 191 genshi_dir.append((statement, closing)) 192 r_statements.append((statement, 193 (expr, closing, directive, attr, attr_val))) 194 195 return r_statements, genshi_dir 204 # map closing tags with their opening tag 205 if is_opening: 206 opened_tags.append(statement) 207 else: 208 closing_tags[id(opened_tags.pop())] = statement 209 # - we operate only on opening statements 210 if is_opening: 211 r_statements.append((statement, 212 (expr, directive, attr, attr_val)) 213 ) 214 assert not opened_tags 215 216 return r_statements, closing_tags 196 217 197 218 def _handle_relatorio_tags(self, tree): … … 207 228 genshi_replace = '{%s}replace' % self.namespaces['py'] 208 229 209 r_statements, genshi_directives = self._relatorio_statements(tree) 210 # We match the opening and closing directives together 211 idx = 0 212 genshi_pairs, inserted = [], [] 213 for statement, closing in genshi_directives: 214 if closing is None: 215 genshi_pairs.append([statement, None]) 216 inserted.append(idx) 217 idx += 1 218 else: 219 genshi_pairs[inserted.pop()][1] = statement 230 r_statements, closing_tags = self._relatorio_statements(tree) 220 231 221 232 for r_node, parsed in r_statements: 222 expr, c_dir, directive, attr, a_val = parsed 223 233 expr, directive, attr, a_val = parsed 234 235 # If the node is a genshi directive statement: 224 236 if directive is not None: 225 # If the node is a genshi directive statement: 226 # - we operate only on opening statement 227 # - we find the nearest ancestor of the closing and opening 228 # statement 229 # - we create a <py:xxx> node 230 # - we add all the node between the opening and closing 231 # statements to this new node 232 # - we replace the opening statement by the <py:for> node 233 # - we delete the closing statement 234 235 if c_dir is not None: 236 # pass the closing statements 237 continue 238 for pair in genshi_pairs: 239 if pair[0] == r_node: 240 opening, closing = pair 241 break 242 243 o_ancestors = list(opening.iterancestors()) 237 238 opening = r_node 239 closing = closing_tags[id(r_node)] 240 241 # - we find the nearest common ancestor of the closing and 242 # opening statements 243 o_ancestors = [] 244 244 c_ancestors = list(closing.iterancestors()) 245 for node in o_ancestors: 246 if node in c_ancestors: 245 ancestor = None 246 for node in opening.iterancestors(): 247 try: 248 idx = c_ancestors.index(node) 249 assert c_ancestors[idx] == node 250 del c_ancestors[idx:] 247 251 ancestor = node 248 252 break 249 253 except ValueError: 254 pass 255 o_ancestors.append(node) 256 assert ancestor is not None, \ 257 "No common ancestor found for opening and closing tag" 258 259 # - we create a <py:xxx> node 250 260 genshi_node = EtreeElement('{%s}%s' % (self.namespaces['py'], 251 directive), 261 directive), 252 262 attrib={attr: a_val}, 253 263 nsmap=self.namespaces) 264 265 # - we add all the nodes between the opening and closing 266 # statements to this new node 254 267 can_append = False 255 268 for node in ancestor.iterchildren(): 256 269 if node in o_ancestors: 257 270 outermost_o_ancestor = node 271 assert outermost_o_ancestor == o_ancestors[-1] 258 272 can_append = True 259 273 continue 260 274 if node in c_ancestors: 261 275 outermost_c_ancestor = node 276 assert outermost_c_ancestor == c_ancestors[-1] 262 277 break 263 278 if can_append: 279 # we are between the opening and closing node 264 280 genshi_node.append(node) 281 282 # - we replace the opening statement by the <py:xxx> node 265 283 ancestor.replace(outermost_o_ancestor, genshi_node) 284 285 # - we delete the closing statement (and its ancestors) 266 286 ancestor.remove(outermost_c_ancestor) 267 287 else: … … 281 301 282 302 def _handle_images(self, tree): 283 "replaces all draw:frame named 'image: ...' by a draw:image node" 303 "replaces all draw:frame named 'image: ...' by a draw:image node" 284 304 draw_name = '{%s}name' % self.namespaces['draw'] 285 305 draw_image = '{%s}image' % self.namespaces['draw'] … … 289 309 d_name = draw.attrib[draw_name] 290 310 attr_expr = "make_href(%s, %r)" % (d_name[7:], d_name[7:]) 291 image_node = EtreeElement(draw_image, 311 image_node = EtreeElement(draw_image, 292 312 attrib={python_attrs: attr_expr}, 293 313 nsmap=self.namespaces) … … 333 353 elif f_info.filename in files: 334 354 stream = files[f_info.filename] 335 self.outzip.writestr(f_info.filename, 355 self.outzip.writestr(f_info.filename, 336 356 output_encode(self.xml_serializer(stream))) 337 357 else: -
relatorio/tests/test_api.py
r80 r89 11 11 # This program is distributed in the hope that it will be useful, but WITHOUT 12 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 14 # details. 15 15 # … … 39 39 "Testing the registration" 40 40 reporting = ReportRepository() 41 reporting.add_report(StubObject, 'text/plain', 41 reporting.add_report(StubObject, 'text/plain', 42 42 os.path.join('templates', 'test.tmpl')) 43 43 … … 49 49 eq_(mime, 'text/plain') 50 50 eq_(report.mimetype, 'text/plain') 51 assert_true(report.fpath.endswith(os.path.join('templates', 51 assert_true(report.fpath.endswith(os.path.join('templates', 52 52 'test.tmpl'))) 53 53 … … 105 105 106 106 a = StubObject(name='Foo') 107 eq_(report(o=a, time="One o'clock").render(), 107 eq_(report(o=a, time="One o'clock").render(), 108 108 "Hi Foo,\nIt's One o'clock to 2 !\n") 109 eq_(report(o=a, time="One o'clock", y=4).render(), 109 eq_(report(o=a, time="One o'clock", y=4).render(), 110 110 "Hi Foo,\nIt's One o'clock to 5 !\n") 111 assert_raises(TypeError, report, a) 111 assert_raises(TypeError, report, a) 112 112 -
relatorio/tests/test_odt.py
r81 r89 12 12 # This program is distributed in the hope that it will be useful, but WITHOUT 13 13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 15 15 # details. 16 16 # … … 37 37 catalog = {'Mes collègues sont:': 'My collegues are:', 38 38 'Bonjour,': 'Hello,', 39 'Je suis un test de templating en odt.': 39 'Je suis un test de templating en odt.': 40 40 'I am an odt templating test', 41 41 'Felix da housecat': unicode('Félix le chat de la maison', … … 55 55 'last_name': unicode('Møller', 'utf8'), 56 56 'ville': unicode('Liège', 'utf8'), 57 'friends': [{'first_name': u'Camille', 57 'friends': [{'first_name': u'Camille', 58 58 'last_name': u'Salauhpe'}, 59 59 {'first_name': u'Mathias', … … 81 81 <text:a xlink:href="relatorio://foo">foo</text:a> 82 82 </b:a>''' % 'urn:text' 83 parsed = self.oot.insert_directives(xml) 83 parsed = self.oot.insert_directives(xml) 84 84 root = lxml.etree.parse(StringIO(xml)).getroot() 85 85 root_parsed = lxml.etree.parse(parsed).getroot() 86 eq_(root_parsed[0].attrib['{http://genshi.edgewall.org/}replace'], 86 eq_(root_parsed[0].attrib['{http://genshi.edgewall.org/}replace'], 87 87 'foo') 88 88 … … 144 144 def test_regexp(self): 145 145 "Testing the regexp used to find relatorio tags" 146 regexp = re.compile(GENSHI_EXPR) 147 group = regexp.match('for each="foo in bar"').groups() 148 eq_(group, ('for each="foo in bar"', None, 'for', ' each="foo in bar"', 149 'each', 'foo in bar')) 150 group = regexp.match('foreach="foo in bar"').groups() 151 eq_(group, ('foreach="foo in bar"', None, None, None, None, None)) 152 group = regexp.match('/for').groups() 153 eq_(group, ('/for', '/', 'for', '', None, None)) 154 group = regexp.match('/for ').groups() 155 eq_(group, ('/for ', '/', 'for', '', None, None)) 156 group = regexp.match('formatLang("en")').groups() 157 eq_(group, ('formatLang("en")', None, None, None, None, None)) 146 # a valid expression 147 group = GENSHI_EXPR.match('for each="foo in bar"').groups() 148 eq_(group, (None, 'for', 'each', 'foo in bar')) 149 150 # invalid expr 151 group = GENSHI_EXPR.match('foreach="foo in bar"').groups() 152 eq_(group, (None, None, None, None)) 153 154 # valid closing tags 155 group = GENSHI_EXPR.match('/for').groups() 156 eq_(group, ('/', 'for', None, None)) 157 group = GENSHI_EXPR.match('/for ').groups() 158 eq_(group, ('/', 'for', None, None)) 159 160 # another non matching expr 161 group = GENSHI_EXPR.match('formatLang("en")').groups() 162 eq_(group, (None, None, None, None)) 158 163 159 164 def test_str(self):
