2 # -*- encoding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Management Solution
6 # Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ##############################################################################
24 # Copyright (C) 2005, Fabien Pinckaers, UCL, FSA
25 # Copyright (C) 2008, P. Christeas
27 # This library is free software; you can redistribute it and/or
28 # modify it under the terms of the GNU Lesser General Public
29 # License as published by the Free Software Foundation; either
30 # version 2.1 of the License, or (at your option) any later version.
32 # This library is distributed in the hope that it will be useful,
33 # but WITHOUT ANY WARRANTY; without even the implied warranty of
34 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
35 # Lesser General Public License for more details.
37 # You should have received a copy of the GNU Lesser General Public
38 # License along with this library; if not, write to the Free Software
39 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
43 import xml.dom.minidom
51 sys.stderr.write(text+"\n");
54 """A box containing plain text.
55 It can have an offset, in chars.
56 Lines can be either text strings, or textbox'es, recursively.
58 def __init__(self,x=0, y=0):
66 if isinstance(self.curline, textbox):
67 self.lines.extend(self.curline.renderlines())
69 self.lines.append(self.curline)
73 if isinstance(self.curline, textbox):
74 self.lines.extend(self.curline.renderlines())
75 elif len(self.curline):
76 self.lines.append(self.curline)
79 def appendtxt(self,txt):
80 """Append some text to the current line.
81 Mimic the HTML behaviour, where all whitespace evaluates to
86 if txt[len(txt)-1].isspace():
88 if bs and not self.endspace:
90 self.curline += txt.strip().replace("\n"," ").replace("\t"," ")
95 def rendertxt(self,xoffset=0):
98 for i in range(self.posy):
100 for i in range(self.posx+xoffset):
103 result+= lineoff+ l +"\n"
106 def renderlines(self,pad=0):
107 """Returns a list of lines, from the current object
108 pad: all lines must be at least pad characters.
112 for i in range(self.posx):
116 if pad and len(l) < pad :
117 for i in range(pad - len(l)):
119 #elif pad and len(l) > pad ?
120 result.append(lineoff+ l+lpad)
124 def haplines(self,arr,offset,cc= ''):
125 """ Horizontaly append lines
127 while (len(self.lines) < len(arr)):
128 self.lines.append("")
130 for i in range(len(self.lines)):
131 while (len(self.lines[i]) < offset):
133 for i in range(len(arr)):
134 self.lines[i] += cc +arr[i]
137 class _flowable(object):
138 def __init__(self, template, doc):
140 '1title': self._tag_title,
141 '1spacer': self._tag_spacer,
142 'para': self._tag_para,
143 'font': self._tag_font,
144 'section': self._tag_section,
145 '1nextFrame': self._tag_next_frame,
146 'blockTable': self._tag_table,
147 '1pageBreak': self._tag_page_break,
148 '1setNextTemplate': self._tag_next_template,
150 self.template = template
155 def warn_nitag(self,tag):
156 if tag not in self.nitags:
157 verbose("Unknown tag \"%s\", please implement it." % tag)
158 self.nitags.append(tag)
160 def _tag_page_break(self, node):
163 def _tag_next_template(self, node):
166 def _tag_next_frame(self, node):
167 result=self.template.frame_stop()
169 result+=self.template.frame_start()
172 def _tag_title(self, node):
176 def _tag_spacer(self, node):
177 length = 1+int(utils.unit_get(node.getAttribute('length')))/35
180 def _tag_table(self, node):
185 if node.hasAttribute('colWidths'):
186 sizes = map(lambda x: utils.unit_get(x), node.getAttribute('colWidths').split(','))
188 for n in node.childNodes:
189 if n.nodeType == node.ELEMENT_NODE and n.localName == 'tr':
191 for m in n.childNodes:
192 if m.nodeType == node.ELEMENT_NODE and m.localName == 'td':
194 self.rec_render_cnodes(m)
201 verbose("computing table sizes..")
205 for i in range(len(tds)):
206 p = int(sizes[i]/Font_size)
207 trl = tds[i].renderlines(pad=p)
208 trt.haplines(trl,off)
209 off += sizes[i]/Font_size
210 saved_tb.curline = trt
216 def _tag_para(self, node):
218 self.rec_render_cnodes(node)
221 def _tag_section(self, node):
223 self.rec_render_cnodes(node)
226 def _tag_font(self, node):
227 """We do ignore fonts.."""
228 self.rec_render_cnodes(node)
230 def rec_render_cnodes(self,node):
231 for n in node.childNodes:
234 def rec_render(self,node):
235 """ Recursive render: fill outarr with text of current node
237 if node.nodeType == node.TEXT_NODE:
238 self.tb.appendtxt(node.data)
239 elif node.nodeType==node.ELEMENT_NODE:
240 if node.localName in self._tags:
241 self._tags[node.localName](node)
243 self.warn_nitag(node.localName)
245 verbose("Unknown nodeType: %d" % node.nodeType)
247 def render(self, node):
249 #result = self.template.start()
250 #result += self.template.frame_start()
251 self.rec_render_cnodes(node)
252 #result += self.template.frame_stop()
253 #result += self.template.end()
254 result = self.tb.rendertxt()
258 class _rml_tmpl_tag(object):
259 def __init__(self, *args):
267 def tag_mergeable(self):
270 class _rml_tmpl_frame(_rml_tmpl_tag):
271 def __init__(self, posx, width):
276 return '<table border="0" width="%d"><tr><td width="%d"> </td><td>' % (self.width+self.posx,self.posx)
281 return '</td></tr></table><br/>'
282 def tag_mergeable(self):
285 # An awfull workaround since I don't really understand the semantic behind merge.
286 def merge(self, frame):
289 class _rml_tmpl_draw_string(_rml_tmpl_tag):
290 def __init__(self, node, style):
291 self.posx = utils.unit_get(node.getAttribute('x'))
292 self.posy = utils.unit_get(node.getAttribute('y'))
294 'drawString': 'left',
295 'drawRightString': 'right',
296 'drawCentredString': 'center'
298 align = aligns[node.localName]
299 self.pos = [(self.posx, self.posy, align, utils.text_get(node), style.get('td'), style.font_size_get('td'))]
302 return "draw string \"%s\" @(%d,%d)..\n" %("txt",self.posx,self.posy)
307 for (x,y,align,txt, style, fs) in self.pos:
310 res+='<td width="%d"></td><td style="%s" width="%d">%s</td>' % (x - posx, style, pos2, txt)
313 res+='<td width="%d" align="right" style="%s">%s</td>' % (x - posx, style, txt)
316 res+='<td width="%d" align="center" style="%s">%s</td>' % ((x - posx)*2, style, txt)
324 class _rml_tmpl_draw_lines(_rml_tmpl_tag):
325 def __init__(self, node, style):
326 coord = [utils.unit_get(x) for x in utils.text_get(node).split(' ')]
330 self.width = coord[2]-coord[0]
331 self.ok = coord[1]==coord[3]
333 self.style = style.get('hr')
336 return "draw lines..\n"
338 return '<table border="0" cellpadding="0" cellspacing="0" width="%d"><tr><td width="%d"></td><td><hr width="100%%" style="margin:0px; %s"></td></tr></table>' % (self.posx+self.width,self.posx,self.style)
342 class _rml_stylesheet(object):
343 def __init__(self, stylesheet, doc):
347 'fontSize': lambda x: ('font-size',str(utils.unit_get(x))+'px'),
348 'alignment': lambda x: ('text-align',str(x))
351 for ps in stylesheet.getElementsByTagName('paraStyle'):
353 attrs = ps.attributes
354 for i in range(attrs.length):
355 name = attrs.item(i).localName
356 attr[name] = ps.getAttribute(name)
360 attrs.append("%s:%s" % self._tags[a](attr[a]))
362 result += "p."+attr['name']+" {"+'; '.join(attrs)+"}\n"
368 class _rml_draw_style(object):
372 'fill': lambda x: {'td': {'color':x.getAttribute('color')}},
373 'setFont': lambda x: {'td': {'font-size':x.getAttribute('size')+'px'}},
374 'stroke': lambda x: {'hr': {'color':x.getAttribute('color')}},
376 def update(self, node):
377 if node.localName in self._styles:
378 result = self._styles[node.localName](node)
380 if key in self.style:
381 self.style[key].update(result[key])
383 self.style[key] = result[key]
384 def font_size_get(self,tag):
385 size = utils.unit_get(self.style.get('td', {}).get('font-size','16'))
389 if not tag in self.style:
391 return ';'.join(['%s:%s' % (x[0],x[1]) for x in self.style[tag].items()])
393 class _rml_template(object):
394 def __init__(self, template):
397 self.template_order = []
398 self.page_template = {}
401 'drawString': _rml_tmpl_draw_string,
402 'drawRightString': _rml_tmpl_draw_string,
403 'drawCentredString': _rml_tmpl_draw_string,
404 'lines': _rml_tmpl_draw_lines
406 self.style = _rml_draw_style()
407 for pt in template.getElementsByTagName('pageTemplate'):
409 id = pt.getAttribute('id')
410 self.template_order.append(id)
411 for tmpl in pt.getElementsByTagName('frame'):
412 posy = int(utils.unit_get(tmpl.getAttribute('y1'))) #+utils.unit_get(tmpl.getAttribute('height')))
413 posx = int(utils.unit_get(tmpl.getAttribute('x1')))
414 frames[(posy,posx,tmpl.getAttribute('id'))] = _rml_tmpl_frame(posx, utils.unit_get(tmpl.getAttribute('width')))
415 for tmpl in template.getElementsByTagName('pageGraphics'):
416 for n in tmpl.childNodes:
417 if n.nodeType==n.ELEMENT_NODE:
418 if n.localName in self._tags:
419 t = self._tags[n.localName](n, self.style)
420 frames[(t.posy,t.posx,n.localName)] = t
426 self.page_template[id] = []
427 for key in range(len(keys)):
428 if key>0 and keys[key-1][0] == keys[key][0]:
429 if type(self.page_template[id][-1]) == type(frames[keys[key]]):
430 if self.page_template[id][-1].tag_mergeable():
431 self.page_template[id][-1].merge(frames[keys[key]])
433 self.page_template[id].append(frames[keys[key]])
434 self.template = self.template_order[0]
436 def _get_style(self):
439 def set_next_template(self):
440 self.template = self.template_order[(self.template_order.index(name)+1) % self.template_order]
443 def set_template(self, name):
447 def frame_start(self):
449 frames = self.page_template[self.template]
453 if self.frame_pos>=len(frames):
458 f = frames[self.frame_pos]
459 result+=f.tag_start()
465 def frame_stop(self):
466 frames = self.page_template[self.template]
467 f = frames[self.frame_pos]
475 return "template end\n"
478 result += self.frame_start()
479 result += self.frame_stop()
482 class _rml_doc(object):
483 def __init__(self, data):
484 self.dom = xml.dom.minidom.parseString(data)
485 self.filename = self.dom.documentElement.getAttribute('filename')
488 def render(self, out):
489 template = _rml_template(self.dom.documentElement.getElementsByTagName('template')[0])
490 f = _flowable(template, self.dom)
491 self.result += f.render(self.dom.documentElement.getElementsByTagName('story')[0])
494 out.write( self.result)
496 def parseString(data, fout=None):
504 fp = StringIO.StringIO()
509 print 'Usage: rml2txt input.rml >output.html'
510 print 'Render the standard input (RML) and output an TXT file'
513 if __name__=="__main__":
515 if sys.argv[1]=='--help':
517 print parseString(file(sys.argv[1], 'r').read()).encode('iso8859-7')
519 print 'Usage: trml2txt input.rml >output.pdf'
520 print 'Try \'trml2txt --help\' for more information.'
522 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: