odf2xhtml.py 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # Copyright (C) 2006-2010 Søren Roug, European Environment Agency
  4. #
  5. # This library is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU Lesser General Public
  7. # License as published by the Free Software Foundation; either
  8. # version 2.1 of the License, or (at your option) any later version.
  9. #
  10. # This library is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. # Lesser General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Lesser General Public
  16. # License along with this library; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. #
  19. # Contributor(s):
  20. #
  21. #import pdb
  22. #pdb.set_trace()
  23. import sys, os.path
  24. sys.path.append(os.path.dirname(__file__))
  25. from xml.sax import handler
  26. from xml.sax.saxutils import escape, quoteattr
  27. from xml.dom import Node
  28. from opendocument import load
  29. from odf.namespaces import ANIMNS, CHARTNS, CONFIGNS, DCNS, DR3DNS, DRAWNS, FONS, \
  30. FORMNS, MATHNS, METANS, NUMBERNS, OFFICENS, PRESENTATIONNS, SCRIPTNS, \
  31. SMILNS, STYLENS, SVGNS, TABLENS, TEXTNS, XLINKNS
  32. # Handling of styles
  33. #
  34. # First there are font face declarations. These set up a font style that will be
  35. # referenced from a text-property. The declaration describes the font making
  36. # it possible for the application to find a similar font should the system not
  37. # have that particular one. The StyleToCSS stores these attributes to be used
  38. # for the CSS2 font declaration.
  39. #
  40. # Then there are default-styles. These set defaults for various style types:
  41. # "text", "paragraph", "section", "ruby", "table", "table-column", "table-row",
  42. # "table-cell", "graphic", "presentation", "drawing-page", "chart".
  43. # Since CSS2 can't refer to another style, ODF2XHTML add these to all
  44. # styles unless overridden.
  45. #
  46. # The real styles are declared in the <style:style> element. They have a
  47. # family referring to the default-styles, and may have a parent style.
  48. #
  49. # Styles have scope. The same name can be used for both paragraph and
  50. # character etc. styles Since CSS2 has no scope we use a prefix. (Not elegant)
  51. # In ODF a style can have a parent, these parents can be chained.
  52. class StyleToCSS:
  53. """ The purpose of the StyleToCSS class is to contain the rules to convert
  54. ODF styles to CSS2. Since it needs the generic fonts, it would probably
  55. make sense to also contain the Styles in a dict as well..
  56. """
  57. def __init__(self):
  58. # Font declarations
  59. self.fontdict = {}
  60. # Fill-images from presentations for backgrounds
  61. self.fillimages = {}
  62. self.ruleconversions = {
  63. (DRAWNS,u'fill-image-name'): self.c_drawfillimage,
  64. (FONS,u"background-color"): self.c_fo,
  65. (FONS,u"border"): self.c_fo,
  66. (FONS,u"border-bottom"): self.c_fo,
  67. (FONS,u"border-left"): self.c_fo,
  68. (FONS,u"border-right"): self.c_fo,
  69. (FONS,u"border-top"): self.c_fo,
  70. (FONS,u"color"): self.c_fo,
  71. (FONS,u"font-family"): self.c_fo,
  72. (FONS,u"font-size"): self.c_fo,
  73. (FONS,u"font-style"): self.c_fo,
  74. (FONS,u"font-variant"): self.c_fo,
  75. (FONS,u"font-weight"): self.c_fo,
  76. (FONS,u"line-height"): self.c_fo,
  77. (FONS,u"margin"): self.c_fo,
  78. (FONS,u"margin-bottom"): self.c_fo,
  79. (FONS,u"margin-left"): self.c_fo,
  80. (FONS,u"margin-right"): self.c_fo,
  81. (FONS,u"margin-top"): self.c_fo,
  82. (FONS,u"min-height"): self.c_fo,
  83. (FONS,u"padding"): self.c_fo,
  84. (FONS,u"padding-bottom"): self.c_fo,
  85. (FONS,u"padding-left"): self.c_fo,
  86. (FONS,u"padding-right"): self.c_fo,
  87. (FONS,u"padding-top"): self.c_fo,
  88. (FONS,u"page-width"): self.c_page_width,
  89. (FONS,u"page-height"): self.c_page_height,
  90. (FONS,u"text-align"): self.c_text_align,
  91. (FONS,u"text-indent") :self.c_fo,
  92. (TABLENS,u'border-model') :self.c_border_model,
  93. (STYLENS,u'column-width') : self.c_width,
  94. (STYLENS,u"font-name"): self.c_fn,
  95. (STYLENS,u'horizontal-pos'): self.c_hp,
  96. (STYLENS,u'text-position'): self.c_text_position,
  97. (STYLENS,u'text-line-through-style'): self.c_text_line_through_style,
  98. (STYLENS,u'text-underline-style'): self.c_text_underline_style,
  99. (STYLENS,u'width') : self.c_width,
  100. # FIXME Should do style:vertical-pos here
  101. }
  102. def save_font(self, name, family, generic):
  103. """ It is possible that the HTML browser doesn't know how to
  104. show a particular font. Fortunately ODF provides generic fallbacks.
  105. Unfortunately they are not the same as CSS2.
  106. CSS2: serif, sans-serif, cursive, fantasy, monospace
  107. ODF: roman, swiss, modern, decorative, script, system
  108. This method put the font and fallback into a dictionary
  109. """
  110. htmlgeneric = "sans-serif"
  111. if generic == "roman": htmlgeneric = "serif"
  112. elif generic == "swiss": htmlgeneric = "sans-serif"
  113. elif generic == "modern": htmlgeneric = "monospace"
  114. elif generic == "decorative": htmlgeneric = "sans-serif"
  115. elif generic == "script": htmlgeneric = "monospace"
  116. elif generic == "system": htmlgeneric = "serif"
  117. self.fontdict[name] = (family, htmlgeneric)
  118. def c_drawfillimage(self, ruleset, sdict, rule, val):
  119. """ Fill a figure with an image. Since CSS doesn't let you resize images
  120. this should really be implemented as an absolutely position <img>
  121. with a width and a height
  122. """
  123. sdict['background-image'] = "url('%s')" % self.fillimages[val]
  124. def c_fo(self, ruleset, sdict, rule, val):
  125. """ XSL formatting attributes """
  126. selector = rule[1]
  127. sdict[selector] = val
  128. def c_border_model(self, ruleset, sdict, rule, val):
  129. """ Convert to CSS2 border model """
  130. if val == 'collapsing':
  131. sdict['border-collapse'] ='collapse'
  132. else:
  133. sdict['border-collapse'] ='separate'
  134. def c_width(self, ruleset, sdict, rule, val):
  135. """ Set width of box """
  136. sdict['width'] = val
  137. def c_text_align(self, ruleset, sdict, rule, align):
  138. """ Text align """
  139. if align == "start": align = "left"
  140. if align == "end": align = "right"
  141. sdict['text-align'] = align
  142. def c_fn(self, ruleset, sdict, rule, fontstyle):
  143. """ Generate the CSS font family
  144. A generic font can be found in two ways. In a <style:font-face>
  145. element or as a font-family-generic attribute in text-properties.
  146. """
  147. generic = ruleset.get((STYLENS,'font-family-generic') )
  148. if generic is not None:
  149. self.save_font(fontstyle, fontstyle, generic)
  150. family, htmlgeneric = self.fontdict.get(fontstyle, (fontstyle, 'serif'))
  151. sdict['font-family'] = '%s, %s' % (family, htmlgeneric)
  152. def c_text_position(self, ruleset, sdict, rule, tp):
  153. """ Text position. This is used e.g. to make superscript and subscript
  154. This attribute can have one or two values.
  155. The first value must be present and specifies the vertical
  156. text position as a percentage that relates to the current font
  157. height or it takes one of the values sub or super. Negative
  158. percentages or the sub value place the text below the
  159. baseline. Positive percentages or the super value place
  160. the text above the baseline. If sub or super is specified,
  161. the application can choose an appropriate text position.
  162. The second value is optional and specifies the font height
  163. as a percentage that relates to the current font-height. If
  164. this value is not specified, an appropriate font height is
  165. used. Although this value may change the font height that
  166. is displayed, it never changes the current font height that
  167. is used for additional calculations.
  168. """
  169. textpos = tp.split(' ')
  170. if len(textpos) == 2 and textpos[0] != "0%":
  171. # Bug in OpenOffice. If vertical-align is 0% - ignore the text size.
  172. sdict['font-size'] = textpos[1]
  173. if textpos[0] == "super":
  174. sdict['vertical-align'] = "33%"
  175. elif textpos[0] == "sub":
  176. sdict['vertical-align'] = "-33%"
  177. else:
  178. sdict['vertical-align'] = textpos[0]
  179. def c_hp(self, ruleset, sdict, rule, hpos):
  180. #FIXME: Frames wrap-style defaults to 'parallel', graphics to 'none'.
  181. # It is properly set in the parent-styles, but the program doesn't
  182. # collect the information.
  183. wrap = ruleset.get((STYLENS,'wrap'),'parallel')
  184. # Can have: from-left, left, center, right, from-inside, inside, outside
  185. if hpos == "center":
  186. sdict['margin-left'] = "auto"
  187. sdict['margin-right'] = "auto"
  188. # else:
  189. # # force it to be *something* then delete it
  190. # sdict['margin-left'] = sdict['margin-right'] = ''
  191. # del sdict['margin-left'], sdict['margin-right']
  192. if hpos in ("right","outside"):
  193. if wrap in ( "left", "parallel","dynamic"):
  194. sdict['float'] = "right"
  195. elif wrap == "run-through":
  196. sdict['position'] = "absolute" # Simulate run-through
  197. sdict['top'] = "0"
  198. sdict['right'] = "0";
  199. else: # No wrapping
  200. sdict['margin-left'] = "auto"
  201. sdict['margin-right'] = "0cm"
  202. elif hpos in ("left", "inside"):
  203. if wrap in ( "right", "parallel","dynamic"):
  204. sdict['float'] = "left"
  205. elif wrap == "run-through":
  206. sdict['position'] = "absolute" # Simulate run-through
  207. sdict['top'] = "0"
  208. sdict['left'] = "0"
  209. else: # No wrapping
  210. sdict['margin-left'] = "0cm"
  211. sdict['margin-right'] = "auto"
  212. elif hpos in ("from-left", "from-inside"):
  213. if wrap in ( "right", "parallel"):
  214. sdict['float'] = "left"
  215. else:
  216. sdict['position'] = "relative" # No wrapping
  217. if (SVGNS,'x') in ruleset:
  218. sdict['left'] = ruleset[(SVGNS,'x')]
  219. def c_page_width(self, ruleset, sdict, rule, val):
  220. """ Set width of box
  221. HTML doesn't really have a page-width. It is always 100% of the browser width
  222. """
  223. sdict['width'] = val
  224. def c_text_underline_style(self, ruleset, sdict, rule, val):
  225. """ Set underline decoration
  226. HTML doesn't really have a page-width. It is always 100% of the browser width
  227. """
  228. if val and val != "none":
  229. sdict['text-decoration'] = "underline"
  230. def c_text_line_through_style(self, ruleset, sdict, rule, val):
  231. """ Set underline decoration
  232. HTML doesn't really have a page-width. It is always 100% of the browser width
  233. """
  234. if val and val != "none":
  235. sdict['text-decoration'] = "line-through"
  236. def c_page_height(self, ruleset, sdict, rule, val):
  237. """ Set height of box """
  238. sdict['height'] = val
  239. def convert_styles(self, ruleset):
  240. """ Rule is a tuple of (namespace, name). If the namespace is '' then
  241. it is already CSS2
  242. """
  243. sdict = {}
  244. procedures=[]
  245. for rule,val in ruleset.items():
  246. if rule[0] == '':
  247. sdict[rule[1]] = val
  248. continue
  249. method = self.ruleconversions.get(rule, None )
  250. if method:
  251. procedures.append([method, ruleset, sdict, rule, val])
  252. # this ensures that the procedures for horizontal position
  253. # are run last! It is important since Python3 makes the order
  254. # of dictionaries unpredictable
  255. for p in filter(lambda x: x[0] != self.c_hp, procedures):
  256. method, ruleset, sdict, rule, val = p
  257. method(ruleset, sdict, rule, val)
  258. for p in filter(lambda x: x[0] == self.c_hp, procedures):
  259. method, ruleset, sdict, rule, val = p
  260. method(ruleset, sdict, rule, val)
  261. return sdict
  262. class TagStack:
  263. def __init__(self):
  264. self.stack = []
  265. def push(self, tag, attrs):
  266. self.stack.append( (tag, attrs) )
  267. def pop(self):
  268. item = self.stack.pop()
  269. return item
  270. def stackparent(self):
  271. item = self.stack[-1]
  272. return item[1]
  273. def rfindattr(self, attr):
  274. """ Find a tag with the given attribute """
  275. for tag, attrs in self.stack:
  276. if attr in attrs:
  277. return attrs[attr]
  278. return None
  279. def count_tags(self, tag):
  280. c = 0
  281. for ttag, tattrs in self.stack:
  282. if ttag == tag: c = c + 1
  283. return c
  284. special_styles = {
  285. 'S-Emphasis':'em',
  286. 'S-Citation':'cite',
  287. 'S-Strong_20_Emphasis':'strong',
  288. 'S-Variable':'var',
  289. 'S-Definition':'dfn',
  290. 'S-Teletype':'tt',
  291. 'P-Heading_20_1':'h1',
  292. 'P-Heading_20_2':'h2',
  293. 'P-Heading_20_3':'h3',
  294. 'P-Heading_20_4':'h4',
  295. 'P-Heading_20_5':'h5',
  296. 'P-Heading_20_6':'h6',
  297. # 'P-Caption':'caption',
  298. 'P-Addressee':'address',
  299. # 'P-List_20_Heading':'dt',
  300. # 'P-List_20_Contents':'dd',
  301. 'P-Preformatted_20_Text':'pre',
  302. # 'P-Table_20_Heading':'th',
  303. # 'P-Table_20_Contents':'td',
  304. # 'P-Text_20_body':'p'
  305. }
  306. #-----------------------------------------------------------------------------
  307. #
  308. # ODFCONTENTHANDLER
  309. #
  310. #-----------------------------------------------------------------------------
  311. class ODF2XHTML(handler.ContentHandler):
  312. """ The ODF2XHTML parses an ODF file and produces XHTML"""
  313. def __init__(self, generate_css=True, embedable=False):
  314. # Tags
  315. self.generate_css = generate_css
  316. self.elements = {
  317. (DCNS, 'title'): (self.s_processcont, self.e_dc_title),
  318. (DCNS, 'language'): (self.s_processcont, self.e_dc_contentlanguage),
  319. (DCNS, 'creator'): (self.s_processcont, self.e_dc_creator),
  320. (DCNS, 'description'): (self.s_processcont, self.e_dc_metatag),
  321. (DCNS, 'date'): (self.s_processcont, self.e_dc_metatag),
  322. (DRAWNS, 'custom-shape'): (self.s_custom_shape, self.e_custom_shape),
  323. (DRAWNS, 'frame'): (self.s_draw_frame, self.e_draw_frame),
  324. (DRAWNS, 'image'): (self.s_draw_image, None),
  325. (DRAWNS, 'fill-image'): (self.s_draw_fill_image, None),
  326. (DRAWNS, "layer-set"):(self.s_ignorexml, None),
  327. (DRAWNS, 'object'): (self.s_draw_object, None),
  328. (DRAWNS, 'object-ole'): (self.s_draw_object_ole, None),
  329. (DRAWNS, 'page'): (self.s_draw_page, self.e_draw_page),
  330. (DRAWNS, 'text-box'): (self.s_draw_textbox, self.e_draw_textbox),
  331. (METANS, 'creation-date'):(self.s_processcont, self.e_dc_metatag),
  332. (METANS, 'generator'):(self.s_processcont, self.e_dc_metatag),
  333. (METANS, 'initial-creator'): (self.s_processcont, self.e_dc_metatag),
  334. (METANS, 'keyword'): (self.s_processcont, self.e_dc_metatag),
  335. (NUMBERNS, "boolean-style"):(self.s_ignorexml, None),
  336. (NUMBERNS, "currency-style"):(self.s_ignorexml, None),
  337. (NUMBERNS, "date-style"):(self.s_ignorexml, None),
  338. (NUMBERNS, "number-style"):(self.s_ignorexml, None),
  339. (NUMBERNS, "text-style"):(self.s_ignorexml, None),
  340. (OFFICENS, "annotation"):(self.s_ignorexml, None),
  341. (OFFICENS, "automatic-styles"):(self.s_office_automatic_styles, None),
  342. (OFFICENS, "document"):(self.s_office_document_content, self.e_office_document_content),
  343. (OFFICENS, "document-content"):(self.s_office_document_content, self.e_office_document_content),
  344. (OFFICENS, "forms"):(self.s_ignorexml, None),
  345. (OFFICENS, "master-styles"):(self.s_office_master_styles, None),
  346. (OFFICENS, "meta"):(self.s_ignorecont, None),
  347. (OFFICENS, "presentation"):(self.s_office_presentation, self.e_office_presentation),
  348. (OFFICENS, "spreadsheet"):(self.s_office_spreadsheet, self.e_office_spreadsheet),
  349. (OFFICENS, "styles"):(self.s_office_styles, None),
  350. (OFFICENS, "text"):(self.s_office_text, self.e_office_text),
  351. (OFFICENS, "scripts"):(self.s_ignorexml, None),
  352. (OFFICENS, "settings"):(self.s_ignorexml, None),
  353. (PRESENTATIONNS, "notes"):(self.s_ignorexml, None),
  354. # (STYLENS, "default-page-layout"):(self.s_style_default_page_layout, self.e_style_page_layout),
  355. (STYLENS, "default-page-layout"):(self.s_ignorexml, None),
  356. (STYLENS, "default-style"):(self.s_style_default_style, self.e_style_default_style),
  357. (STYLENS, "drawing-page-properties"):(self.s_style_handle_properties, None),
  358. (STYLENS, "font-face"):(self.s_style_font_face, None),
  359. # (STYLENS, "footer"):(self.s_style_footer, self.e_style_footer),
  360. # (STYLENS, "footer-style"):(self.s_style_footer_style, None),
  361. (STYLENS, "graphic-properties"):(self.s_style_handle_properties, None),
  362. (STYLENS, "handout-master"):(self.s_ignorexml, None),
  363. # (STYLENS, "header"):(self.s_style_header, self.e_style_header),
  364. # (STYLENS, "header-footer-properties"):(self.s_style_handle_properties, None),
  365. # (STYLENS, "header-style"):(self.s_style_header_style, None),
  366. (STYLENS, "master-page"):(self.s_style_master_page, None),
  367. (STYLENS, "page-layout-properties"):(self.s_style_handle_properties, None),
  368. (STYLENS, "page-layout"):(self.s_style_page_layout, self.e_style_page_layout),
  369. # (STYLENS, "page-layout"):(self.s_ignorexml, None),
  370. (STYLENS, "paragraph-properties"):(self.s_style_handle_properties, None),
  371. (STYLENS, "style"):(self.s_style_style, self.e_style_style),
  372. (STYLENS, "table-cell-properties"):(self.s_style_handle_properties, None),
  373. (STYLENS, "table-column-properties"):(self.s_style_handle_properties, None),
  374. (STYLENS, "table-properties"):(self.s_style_handle_properties, None),
  375. (STYLENS, "text-properties"):(self.s_style_handle_properties, None),
  376. (SVGNS, 'desc'): (self.s_ignorexml, None),
  377. (TABLENS, 'covered-table-cell'): (self.s_ignorexml, None),
  378. (TABLENS, 'table-cell'): (self.s_table_table_cell, self.e_table_table_cell),
  379. (TABLENS, 'table-column'): (self.s_table_table_column, None),
  380. (TABLENS, 'table-row'): (self.s_table_table_row, self.e_table_table_row),
  381. (TABLENS, 'table'): (self.s_table_table, self.e_table_table),
  382. (TEXTNS, 'a'): (self.s_text_a, self.e_text_a),
  383. (TEXTNS, "alphabetical-index-source"):(self.s_text_x_source, self.e_text_x_source),
  384. (TEXTNS, "bibliography-configuration"):(self.s_ignorexml, None),
  385. (TEXTNS, "bibliography-source"):(self.s_text_x_source, self.e_text_x_source),
  386. (TEXTNS, 'bookmark'): (self.s_text_bookmark, None),
  387. (TEXTNS, 'bookmark-start'): (self.s_text_bookmark, None),
  388. (TEXTNS, 'bookmark-ref'): (self.s_text_bookmark_ref, self.e_text_a),
  389. (TEXTNS, 'bookmark-ref-start'): (self.s_text_bookmark_ref, None),
  390. (TEXTNS, 'h'): (self.s_text_h, self.e_text_h),
  391. (TEXTNS, "illustration-index-source"):(self.s_text_x_source, self.e_text_x_source),
  392. (TEXTNS, 'line-break'):(self.s_text_line_break, None),
  393. (TEXTNS, "linenumbering-configuration"):(self.s_ignorexml, None),
  394. (TEXTNS, "list"):(self.s_text_list, self.e_text_list),
  395. (TEXTNS, "list-item"):(self.s_text_list_item, self.e_text_list_item),
  396. (TEXTNS, "list-level-style-bullet"):(self.s_text_list_level_style_bullet, self.e_text_list_level_style_bullet),
  397. (TEXTNS, "list-level-style-number"):(self.s_text_list_level_style_number, self.e_text_list_level_style_number),
  398. (TEXTNS, "list-style"):(None, None),
  399. (TEXTNS, "note"):(self.s_text_note, None),
  400. (TEXTNS, "note-body"):(self.s_text_note_body, self.e_text_note_body),
  401. (TEXTNS, "note-citation"):(None, self.e_text_note_citation),
  402. (TEXTNS, "notes-configuration"):(self.s_ignorexml, None),
  403. (TEXTNS, "object-index-source"):(self.s_text_x_source, self.e_text_x_source),
  404. (TEXTNS, 'p'): (self.s_text_p, self.e_text_p),
  405. (TEXTNS, 's'): (self.s_text_s, None),
  406. (TEXTNS, 'span'): (self.s_text_span, self.e_text_span),
  407. (TEXTNS, 'tab'): (self.s_text_tab, None),
  408. (TEXTNS, "table-index-source"):(self.s_text_x_source, self.e_text_x_source),
  409. (TEXTNS, "table-of-content-source"):(self.s_text_x_source, self.e_text_x_source),
  410. (TEXTNS, "user-index-source"):(self.s_text_x_source, self.e_text_x_source),
  411. }
  412. if embedable:
  413. self.set_embedable()
  414. self._resetobject()
  415. def set_plain(self):
  416. """ Tell the parser to not generate CSS """
  417. self.generate_css = False
  418. def set_embedable(self):
  419. """ Tells the converter to only output the parts inside the <body>"""
  420. self.elements[(OFFICENS, u"text")] = (None,None)
  421. self.elements[(OFFICENS, u"spreadsheet")] = (None,None)
  422. self.elements[(OFFICENS, u"presentation")] = (None,None)
  423. self.elements[(OFFICENS, u"document-content")] = (None,None)
  424. def add_style_file(self, stylefilename, media=None):
  425. """ Add a link to an external style file.
  426. Also turns of the embedding of styles in the HTML
  427. """
  428. self.use_internal_css = False
  429. self.stylefilename = stylefilename
  430. if media:
  431. self.metatags.append('<link rel="stylesheet" type="text/css" href="%s" media="%s"/>\n' % (stylefilename,media))
  432. else:
  433. self.metatags.append('<link rel="stylesheet" type="text/css" href="%s"/>\n' % (stylefilename))
  434. def _resetfootnotes(self):
  435. # Footnotes and endnotes
  436. self.notedict = {}
  437. self.currentnote = 0
  438. self.notebody = ''
  439. def _resetobject(self):
  440. self.lines = []
  441. self._wfunc = self._wlines
  442. self.xmlfile = ''
  443. self.title = ''
  444. self.language = ''
  445. self.creator = ''
  446. self.data = []
  447. self.tagstack = TagStack()
  448. self.htmlstack = []
  449. self.pstack = []
  450. self.processelem = True
  451. self.processcont = True
  452. self.listtypes = {}
  453. self.headinglevels = [0, 0,0,0,0,0, 0,0,0,0,0] # level 0 to 10
  454. self.use_internal_css = True
  455. self.cs = StyleToCSS()
  456. self.anchors = {}
  457. # Style declarations
  458. self.stylestack = []
  459. self.styledict = {}
  460. self.currentstyle = None
  461. self._resetfootnotes()
  462. # Tags from meta.xml
  463. self.metatags = []
  464. def writeout(self, s):
  465. if s != '':
  466. self._wfunc(s)
  467. def writedata(self):
  468. d = ''.join(self.data)
  469. if d != '':
  470. self.writeout(escape(d))
  471. def opentag(self, tag, attrs={}, block=False):
  472. """ Create an open HTML tag """
  473. self.htmlstack.append((tag,attrs,block))
  474. a = []
  475. for key,val in attrs.items():
  476. a.append('''%s=%s''' % (key, quoteattr(val)))
  477. if len(a) == 0:
  478. self.writeout("<%s>" % tag)
  479. else:
  480. self.writeout("<%s %s>" % (tag, " ".join(a)))
  481. if block == True:
  482. self.writeout("\n")
  483. def closetag(self, tag, block=True):
  484. """ Close an open HTML tag """
  485. self.htmlstack.pop()
  486. self.writeout("</%s>" % tag)
  487. if block == True:
  488. self.writeout("\n")
  489. def emptytag(self, tag, attrs={}):
  490. a = []
  491. for key,val in attrs.items():
  492. a.append('''%s=%s''' % (key, quoteattr(val)))
  493. self.writeout("<%s %s/>\n" % (tag, " ".join(a)))
  494. #--------------------------------------------------
  495. # Interface to parser
  496. #--------------------------------------------------
  497. def characters(self, data):
  498. if self.processelem and self.processcont:
  499. self.data.append(data)
  500. def startElementNS(self, tag, qname, attrs):
  501. self.pstack.append( (self.processelem, self.processcont) )
  502. if self.processelem:
  503. method = self.elements.get(tag, (None, None) )[0]
  504. if method:
  505. self.handle_starttag(tag, method, attrs)
  506. else:
  507. self.unknown_starttag(tag,attrs)
  508. self.tagstack.push( tag, attrs )
  509. def endElementNS(self, tag, qname):
  510. stag, attrs = self.tagstack.pop()
  511. if self.processelem:
  512. method = self.elements.get(tag, (None, None) )[1]
  513. if method:
  514. self.handle_endtag(tag, attrs, method)
  515. else:
  516. self.unknown_endtag(tag, attrs)
  517. self.processelem, self.processcont = self.pstack.pop()
  518. #--------------------------------------------------
  519. def handle_starttag(self, tag, method, attrs):
  520. method(tag,attrs)
  521. def handle_endtag(self, tag, attrs, method):
  522. method(tag, attrs)
  523. def unknown_starttag(self, tag, attrs):
  524. pass
  525. def unknown_endtag(self, tag, attrs):
  526. pass
  527. def s_ignorexml(self, tag, attrs):
  528. """ Ignore this xml element and all children of it
  529. It will automatically stop ignoring
  530. """
  531. self.processelem = False
  532. def s_ignorecont(self, tag, attrs):
  533. """ Stop processing the text nodes """
  534. self.processcont = False
  535. def s_processcont(self, tag, attrs):
  536. """ Start processing the text nodes """
  537. self.processcont = True
  538. def classname(self, attrs):
  539. """ Generate a class name from a style name """
  540. c = attrs.get((TEXTNS,'style-name'),'')
  541. c = c.replace(".","_")
  542. return c
  543. def get_anchor(self, name):
  544. """ Create a unique anchor id for a href name """
  545. if name not in self.anchors:
  546. self.anchors[name] = "anchor%03d" % (len(self.anchors) + 1)
  547. return self.anchors.get(name)
  548. #--------------------------------------------------
  549. def purgedata(self):
  550. self.data = []
  551. #-----------------------------------------------------------------------------
  552. #
  553. # Handle meta data
  554. #
  555. #-----------------------------------------------------------------------------
  556. def e_dc_title(self, tag, attrs):
  557. """ Get the title from the meta data and create a HTML <title>
  558. """
  559. self.title = ''.join(self.data)
  560. #self.metatags.append('<title>%s</title>\n' % escape(self.title))
  561. self.data = []
  562. def e_dc_metatag(self, tag, attrs):
  563. """ Any other meta data is added as a <meta> element
  564. """
  565. self.metatags.append('<meta name="%s" content=%s/>\n' % (tag[1], quoteattr(''.join(self.data))))
  566. self.data = []
  567. def e_dc_contentlanguage(self, tag, attrs):
  568. """ Set the content language. Identifies the targeted audience
  569. """
  570. self.language = ''.join(self.data)
  571. self.metatags.append('<meta http-equiv="content-language" content="%s"/>\n' % escape(self.language))
  572. self.data = []
  573. def e_dc_creator(self, tag, attrs):
  574. """ Set the content creator. Identifies the targeted audience
  575. """
  576. self.creator = ''.join(self.data)
  577. self.metatags.append('<meta http-equiv="creator" content="%s"/>\n' % escape(self.creator))
  578. self.data = []
  579. def s_custom_shape(self, tag, attrs):
  580. """ A <draw:custom-shape> is made into a <div> in HTML which is then styled
  581. """
  582. anchor_type = attrs.get((TEXTNS,'anchor-type'),'notfound')
  583. htmltag = 'div'
  584. name = "G-" + attrs.get( (DRAWNS,'style-name'), "")
  585. if name == 'G-':
  586. name = "PR-" + attrs.get( (PRESENTATIONNS,'style-name'), "")
  587. name = name.replace(".","_")
  588. if anchor_type == "paragraph":
  589. style = 'position:absolute;'
  590. elif anchor_type == 'char':
  591. style = "position:absolute;"
  592. elif anchor_type == 'as-char':
  593. htmltag = 'div'
  594. style = ''
  595. else:
  596. style = "position: absolute;"
  597. if (SVGNS,"width")in attrs:
  598. style = style + "width:" + attrs[(SVGNS,"width")] + ";"
  599. if (SVGNS,"height") in attrs:
  600. style = style + "height:" + attrs[(SVGNS,"height")] + ";"
  601. if (SVGNS,"x") in attrs:
  602. style = style + "left:" + attrs[(SVGNS,"x")] + ";"
  603. if (SVGNS,"y") in attrs:
  604. style = style + "top:" + attrs[(SVGNS,"y")] + ";"
  605. if self.generate_css:
  606. self.opentag(htmltag, {'class': name, 'style': style})
  607. else:
  608. self.opentag(htmltag)
  609. def e_custom_shape(self, tag, attrs):
  610. """ End the <draw:frame>
  611. """
  612. self.closetag('div')
  613. def s_draw_frame(self, tag, attrs):
  614. """ A <draw:frame> is made into a <div> in HTML which is then styled
  615. """
  616. anchor_type = attrs.get((TEXTNS,'anchor-type'),'notfound')
  617. htmltag = 'div'
  618. name = "G-" + attrs.get( (DRAWNS,'style-name'), "")
  619. if name == 'G-':
  620. name = "PR-" + attrs.get( (PRESENTATIONNS,'style-name'), "")
  621. name = name.replace(".","_")
  622. if anchor_type == "paragraph":
  623. style = 'position:relative;'
  624. elif anchor_type == 'char':
  625. style = "position:relative;"
  626. elif anchor_type == 'as-char':
  627. htmltag = 'div'
  628. style = ''
  629. else:
  630. style = "position:absolute;"
  631. if (SVGNS,"width") in attrs:
  632. style = style + "width:" + attrs[(SVGNS,"width")] + ";"
  633. if (SVGNS,"height") in attrs:
  634. style = style + "height:" + attrs[(SVGNS,"height")] + ";"
  635. if (SVGNS,"x") in attrs:
  636. style = style + "left:" + attrs[(SVGNS,"x")] + ";"
  637. if (SVGNS,"y") in attrs:
  638. style = style + "top:" + attrs[(SVGNS,"y")] + ";"
  639. if self.generate_css:
  640. self.opentag(htmltag, {'class': name, 'style': style})
  641. else:
  642. self.opentag(htmltag)
  643. def e_draw_frame(self, tag, attrs):
  644. """ End the <draw:frame>
  645. """
  646. self.closetag('div')
  647. def s_draw_fill_image(self, tag, attrs):
  648. name = attrs.get( (DRAWNS,'name'), "NoName")
  649. imghref = attrs[(XLINKNS,"href")]
  650. imghref = self.rewritelink(imghref)
  651. self.cs.fillimages[name] = imghref
  652. def rewritelink(self, imghref):
  653. """ Intended to be overloaded if you don't store your pictures
  654. in a Pictures subfolder
  655. """
  656. return imghref
  657. def s_draw_image(self, tag, attrs):
  658. """ A <draw:image> becomes an <img/> element
  659. """
  660. parent = self.tagstack.stackparent()
  661. anchor_type = parent.get((TEXTNS,'anchor-type'))
  662. imghref = attrs[(XLINKNS,"href")]
  663. imghref = self.rewritelink(imghref)
  664. htmlattrs = {'alt':"", 'src':imghref }
  665. if self.generate_css:
  666. if anchor_type != "char":
  667. htmlattrs['style'] = "display: block;"
  668. self.emptytag('img', htmlattrs)
  669. def s_draw_object(self, tag, attrs):
  670. """ A <draw:object> is embedded object in the document (e.g. spreadsheet in presentation).
  671. """
  672. objhref = attrs[(XLINKNS,"href")]
  673. # Remove leading "./": from "./Object 1" to "Object 1"
  674. # objhref = objhref [2:]
  675. # Not using os.path.join since it fails to find the file on Windows.
  676. # objcontentpath = '/'.join([objhref, 'content.xml'])
  677. ################################
  678. # fixed: self.document.childnodes ===> self.document.childobjects
  679. # 2016-02-19 G.K.
  680. ################################
  681. for c in self.document.childobjects:
  682. if c.folder == objhref:
  683. self._walknode(c.topnode)
  684. def s_draw_object_ole(self, tag, attrs):
  685. """ A <draw:object-ole> is embedded OLE object in the document (e.g. MS Graph).
  686. """
  687. class_id = attrs[(DRAWNS,"class-id")]
  688. if class_id and class_id.lower() == "00020803-0000-0000-c000-000000000046": ## Microsoft Graph 97 Chart
  689. tagattrs = { 'name':'object_ole_graph', 'class':'ole-graph' }
  690. self.opentag('a', tagattrs)
  691. self.closetag('a', tagattrs)
  692. def s_draw_page(self, tag, attrs):
  693. """ A <draw:page> is a slide in a presentation. We use a <fieldset> element in HTML.
  694. Therefore if you convert a ODP file, you get a series of <fieldset>s.
  695. Override this for your own purpose.
  696. """
  697. name = attrs.get( (DRAWNS,'name'), "NoName")
  698. stylename = attrs.get( (DRAWNS,'style-name'), "")
  699. stylename = stylename.replace(".","_")
  700. masterpage = attrs.get( (DRAWNS,'master-page-name'),"")
  701. masterpage = masterpage.replace(".","_")
  702. if self.generate_css:
  703. self.opentag('fieldset', {'class':"DP-%s MP-%s" % (stylename, masterpage) })
  704. else:
  705. self.opentag('fieldset')
  706. self.opentag('legend')
  707. self.writeout(escape(name))
  708. self.closetag('legend')
  709. def e_draw_page(self, tag, attrs):
  710. self.closetag('fieldset')
  711. def s_draw_textbox(self, tag, attrs):
  712. style = ''
  713. if (FONS,"min-height") in attrs:
  714. style = style + "min-height:" + attrs[(FONS,"min-height")] + ";"
  715. self.opentag('div')
  716. # self.opentag('div', {'style': style})
  717. def e_draw_textbox(self, tag, attrs):
  718. """ End the <draw:text-box>
  719. """
  720. self.closetag('div')
  721. def html_body(self, tag, attrs):
  722. self.writedata()
  723. if self.generate_css and self.use_internal_css:
  724. self.opentag('style', {'type':"text/css"}, True)
  725. self.writeout('/*<![CDATA[*/\n')
  726. self.generate_stylesheet()
  727. self.writeout('/*]]>*/\n')
  728. self.closetag('style')
  729. self.purgedata()
  730. self.closetag('head')
  731. self.opentag('body', block=True)
  732. default_styles = """
  733. img { width: 100%; height: 100%; }
  734. * { padding: 0; margin: 0; background-color:white; }
  735. body { margin: 0 1em; }
  736. ol, ul { padding-left: 2em; }
  737. """
  738. def generate_stylesheet(self):
  739. for name in self.stylestack:
  740. styles = self.styledict.get(name)
  741. # Preload with the family's default style
  742. if '__style-family'in styles and styles['__style-family'] in self.styledict:
  743. familystyle = self.styledict[styles['__style-family']].copy()
  744. del styles['__style-family']
  745. for style, val in styles.items():
  746. familystyle[style] = val
  747. styles = familystyle
  748. # Resolve the remaining parent styles
  749. while '__parent-style-name' in styles and styles['__parent-style-name'] in self.styledict:
  750. parentstyle = self.styledict[styles['__parent-style-name']].copy()
  751. del styles['__parent-style-name']
  752. for style, val in styles.items():
  753. parentstyle[style] = val
  754. styles = parentstyle
  755. self.styledict[name] = styles
  756. # Write the styles to HTML
  757. self.writeout(self.default_styles)
  758. for name in self.stylestack:
  759. styles = self.styledict.get(name)
  760. css2 = self.cs.convert_styles(styles)
  761. self.writeout("%s {\n" % name)
  762. for style, val in css2.items():
  763. self.writeout("\t%s: %s;\n" % (style, val) )
  764. self.writeout("}\n")
  765. def generate_footnotes(self):
  766. if self.currentnote == 0:
  767. return
  768. if self.generate_css:
  769. self.opentag('ol', {'style':'border-top: 1px solid black'}, True)
  770. else:
  771. self.opentag('ol')
  772. for key in range(1,self.currentnote+1):
  773. note = self.notedict[key]
  774. # for key,note in self.notedict.items():
  775. self.opentag('li', { 'id':"footnote-%d" % key })
  776. # self.opentag('sup')
  777. # self.writeout(escape(note['citation']))
  778. # self.closetag('sup', False)
  779. self.writeout(note['body'])
  780. self.closetag('li')
  781. self.closetag('ol')
  782. def s_office_automatic_styles(self, tag, attrs):
  783. if self.xmlfile == 'styles.xml':
  784. self.autoprefix = "A"
  785. else:
  786. self.autoprefix = ""
  787. def s_office_document_content(self, tag, attrs):
  788. """ First tag in the content.xml file"""
  789. self.writeout('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" ')
  790. self.writeout('"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n')
  791. self.opentag('html', {'xmlns':"http://www.w3.org/1999/xhtml"}, True)
  792. self.opentag('head', block=True)
  793. self.emptytag('meta', { 'http-equiv':"Content-Type", 'content':"text/html;charset=UTF-8"})
  794. for metaline in self.metatags:
  795. self.writeout(metaline)
  796. self.writeout('<title>%s</title>\n' % escape(self.title))
  797. def e_office_document_content(self, tag, attrs):
  798. """ Last tag """
  799. self.closetag('html')
  800. def s_office_master_styles(self, tag, attrs):
  801. """ """
  802. def s_office_presentation(self, tag, attrs):
  803. """ For some odd reason, OpenOffice Impress doesn't define a default-style
  804. for the 'paragraph'. We therefore force a standard when we see
  805. it is a presentation
  806. """
  807. self.styledict['p'] = {(FONS,u'font-size'): u"24pt" }
  808. self.styledict['presentation'] = {(FONS,u'font-size'): u"24pt" }
  809. self.html_body(tag, attrs)
  810. def e_office_presentation(self, tag, attrs):
  811. self.generate_footnotes()
  812. self.closetag('body')
  813. def s_office_spreadsheet(self, tag, attrs):
  814. self.html_body(tag, attrs)
  815. def e_office_spreadsheet(self, tag, attrs):
  816. self.generate_footnotes()
  817. self.closetag('body')
  818. def s_office_styles(self, tag, attrs):
  819. self.autoprefix = ""
  820. def s_office_text(self, tag, attrs):
  821. """ OpenDocument text """
  822. self.styledict['frame'] = { (STYLENS,'wrap'): u'parallel'}
  823. self.html_body(tag, attrs)
  824. def e_office_text(self, tag, attrs):
  825. self.generate_footnotes()
  826. self.closetag('body')
  827. def s_style_handle_properties(self, tag, attrs):
  828. """ Copy all attributes to a struct.
  829. We will later convert them to CSS2
  830. """
  831. for key,attr in attrs.items():
  832. self.styledict[self.currentstyle][key] = attr
  833. familymap = {'frame':'frame', 'paragraph':'p', 'presentation':'presentation',
  834. 'text':'span','section':'div',
  835. 'table':'table','table-cell':'td','table-column':'col',
  836. 'table-row':'tr','graphic':'graphic' }
  837. def s_style_default_style(self, tag, attrs):
  838. """ A default style is like a style on an HTML tag
  839. """
  840. family = attrs[(STYLENS,'family')]
  841. htmlfamily = self.familymap.get(family,'unknown')
  842. self.currentstyle = htmlfamily
  843. # self.stylestack.append(self.currentstyle)
  844. self.styledict[self.currentstyle] = {}
  845. def e_style_default_style(self, tag, attrs):
  846. self.currentstyle = None
  847. def s_style_font_face(self, tag, attrs):
  848. """ It is possible that the HTML browser doesn't know how to
  849. show a particular font. Luckily ODF provides generic fallbacks
  850. Unfortunately they are not the same as CSS2.
  851. CSS2: serif, sans-serif, cursive, fantasy, monospace
  852. ODF: roman, swiss, modern, decorative, script, system
  853. """
  854. name = attrs[(STYLENS,"name")]
  855. family = attrs[(SVGNS,"font-family")]
  856. generic = attrs.get( (STYLENS,'font-family-generic'),"" )
  857. self.cs.save_font(name, family, generic)
  858. def s_style_footer(self, tag, attrs):
  859. self.opentag('div', { 'id':"footer" })
  860. self.purgedata()
  861. def e_style_footer(self, tag, attrs):
  862. self.writedata()
  863. self.closetag('div')
  864. self.purgedata()
  865. def s_style_footer_style(self, tag, attrs):
  866. self.currentstyle = "@print #footer"
  867. self.stylestack.append(self.currentstyle)
  868. self.styledict[self.currentstyle] = {}
  869. def s_style_header(self, tag, attrs):
  870. self.opentag('div', { 'id':"header" })
  871. self.purgedata()
  872. def e_style_header(self, tag, attrs):
  873. self.writedata()
  874. self.closetag('div')
  875. self.purgedata()
  876. def s_style_header_style(self, tag, attrs):
  877. self.currentstyle = "@print #header"
  878. self.stylestack.append(self.currentstyle)
  879. self.styledict[self.currentstyle] = {}
  880. def s_style_default_page_layout(self, tag, attrs):
  881. """ Collect the formatting for the default page layout style.
  882. """
  883. self.currentstyle = "@page"
  884. self.stylestack.append(self.currentstyle)
  885. self.styledict[self.currentstyle] = {}
  886. def s_style_page_layout(self, tag, attrs):
  887. """ Collect the formatting for the page layout style.
  888. This won't work in CSS 2.1, as page identifiers are not allowed.
  889. It is legal in CSS3, but the rest of the application doesn't specify when to use what page layout
  890. """
  891. name = attrs[(STYLENS,'name')]
  892. name = name.replace(".","_")
  893. self.currentstyle = ".PL-" + name
  894. self.stylestack.append(self.currentstyle)
  895. self.styledict[self.currentstyle] = {}
  896. def e_style_page_layout(self, tag, attrs):
  897. """ End this style
  898. """
  899. self.currentstyle = None
  900. def s_style_master_page(self, tag, attrs):
  901. """ Collect the formatting for the page layout style.
  902. """
  903. name = attrs[(STYLENS,'name')]
  904. name = name.replace(".","_")
  905. self.currentstyle = ".MP-" + name
  906. self.stylestack.append(self.currentstyle)
  907. self.styledict[self.currentstyle] = {('','position'):'relative'}
  908. # Then load the pagelayout style if we find it
  909. pagelayout = attrs.get( (STYLENS,'page-layout-name'), None)
  910. if pagelayout:
  911. pagelayout = ".PL-" + pagelayout
  912. if pagelayout in self.styledict:
  913. styles = self.styledict[pagelayout]
  914. for style, val in styles.items():
  915. self.styledict[self.currentstyle][style] = val
  916. else:
  917. self.styledict[self.currentstyle]['__parent-style-name'] = pagelayout
  918. self.s_ignorexml(tag, attrs)
  919. # Short prefixes for class selectors
  920. _familyshort = {'drawing-page':'DP', 'paragraph':'P', 'presentation':'PR',
  921. 'text':'S', 'section':'D',
  922. 'table':'T', 'table-cell':'TD', 'table-column':'TC',
  923. 'table-row':'TR', 'graphic':'G' }
  924. def s_style_style(self, tag, attrs):
  925. """ Collect the formatting for the style.
  926. Styles have scope. The same name can be used for both paragraph and
  927. character styles Since CSS has no scope we use a prefix. (Not elegant)
  928. In ODF a style can have a parent, these parents can be chained.
  929. We may not have encountered the parent yet, but if we have, we resolve it.
  930. """
  931. name = attrs[(STYLENS,'name')]
  932. name = name.replace(".","_")
  933. family = attrs[(STYLENS,'family')]
  934. htmlfamily = self.familymap.get(family,'unknown')
  935. sfamily = self._familyshort.get(family,'X')
  936. name = "%s%s-%s" % (self.autoprefix, sfamily, name)
  937. parent = attrs.get( (STYLENS,'parent-style-name') )
  938. self.currentstyle = special_styles.get(name,"."+name)
  939. self.stylestack.append(self.currentstyle)
  940. if self.currentstyle not in self.styledict:
  941. self.styledict[self.currentstyle] = {}
  942. self.styledict[self.currentstyle]['__style-family'] = htmlfamily
  943. # Then load the parent style if we find it
  944. if parent:
  945. parent = "%s-%s" % (sfamily, parent)
  946. parent = special_styles.get(parent, "."+parent)
  947. if parent in self.styledict:
  948. styles = self.styledict[parent]
  949. for style, val in styles.items():
  950. self.styledict[self.currentstyle][style] = val
  951. else:
  952. self.styledict[self.currentstyle]['__parent-style-name'] = parent
  953. def e_style_style(self, tag, attrs):
  954. """ End this style
  955. """
  956. self.currentstyle = None
  957. def s_table_table(self, tag, attrs):
  958. """ Start a table
  959. """
  960. c = attrs.get( (TABLENS,'style-name'), None)
  961. if c and self.generate_css:
  962. c = c.replace(".","_")
  963. self.opentag('table',{ 'class': "T-%s" % c })
  964. else:
  965. self.opentag('table')
  966. self.purgedata()
  967. def e_table_table(self, tag, attrs):
  968. """ End a table
  969. """
  970. self.writedata()
  971. self.closetag('table')
  972. self.purgedata()
  973. def s_table_table_cell(self, tag, attrs):
  974. """ Start a table cell """
  975. #FIXME: number-columns-repeated § 8.1.3
  976. #repeated = int(attrs.get( (TABLENS,'number-columns-repeated'), 1))
  977. htmlattrs = {}
  978. rowspan = attrs.get( (TABLENS,'number-rows-spanned') )
  979. if rowspan:
  980. htmlattrs['rowspan'] = rowspan
  981. colspan = attrs.get( (TABLENS,'number-columns-spanned') )
  982. if colspan:
  983. htmlattrs['colspan'] = colspan
  984. c = attrs.get( (TABLENS,'style-name') )
  985. if c:
  986. htmlattrs['class'] = 'TD-%s' % c.replace(".","_")
  987. self.opentag('td', htmlattrs)
  988. self.purgedata()
  989. def e_table_table_cell(self, tag, attrs):
  990. """ End a table cell """
  991. self.writedata()
  992. self.closetag('td')
  993. self.purgedata()
  994. def s_table_table_column(self, tag, attrs):
  995. """ Start a table column """
  996. c = attrs.get( (TABLENS,'style-name'), None)
  997. repeated = int(attrs.get( (TABLENS,'number-columns-repeated'), 1))
  998. htmlattrs = {}
  999. if c:
  1000. htmlattrs['class'] = "TC-%s" % c.replace(".","_")
  1001. for x in range(repeated):
  1002. self.emptytag('col', htmlattrs)
  1003. self.purgedata()
  1004. def s_table_table_row(self, tag, attrs):
  1005. """ Start a table row """
  1006. #FIXME: table:number-rows-repeated
  1007. c = attrs.get( (TABLENS,'style-name'), None)
  1008. htmlattrs = {}
  1009. if c:
  1010. htmlattrs['class'] = "TR-%s" % c.replace(".","_")
  1011. self.opentag('tr', htmlattrs)
  1012. self.purgedata()
  1013. def e_table_table_row(self, tag, attrs):
  1014. """ End a table row """
  1015. self.writedata()
  1016. self.closetag('tr')
  1017. self.purgedata()
  1018. def s_text_a(self, tag, attrs):
  1019. """ Anchors start """
  1020. self.writedata()
  1021. href = attrs[(XLINKNS,"href")].split("|")[0]
  1022. if href[0] == "#":
  1023. href = "#" + self.get_anchor(href[1:])
  1024. self.opentag('a', {'href':href})
  1025. self.purgedata()
  1026. def e_text_a(self, tag, attrs):
  1027. """ End an anchor or bookmark reference """
  1028. self.writedata()
  1029. self.closetag('a', False)
  1030. self.purgedata()
  1031. def s_text_bookmark(self, tag, attrs):
  1032. """ Bookmark definition """
  1033. name = attrs[(TEXTNS,'name')]
  1034. html_id = self.get_anchor(name)
  1035. self.writedata()
  1036. self.opentag('span', {'id':html_id})
  1037. self.closetag('span', False)
  1038. self.purgedata()
  1039. def s_text_bookmark_ref(self, tag, attrs):
  1040. """ Bookmark reference """
  1041. name = attrs[(TEXTNS,'ref-name')]
  1042. html_id = "#" + self.get_anchor(name)
  1043. self.writedata()
  1044. self.opentag('a', {'href':html_id})
  1045. self.purgedata()
  1046. def s_text_h(self, tag, attrs):
  1047. """ Headings start """
  1048. level = int(attrs[(TEXTNS,'outline-level')])
  1049. if level > 6: level = 6 # Heading levels go only to 6 in XHTML
  1050. if level < 1: level = 1
  1051. self.headinglevels[level] = self.headinglevels[level] + 1
  1052. name = self.classname(attrs)
  1053. for x in range(level + 1,10):
  1054. self.headinglevels[x] = 0
  1055. special = special_styles.get("P-"+name)
  1056. if special or not self.generate_css:
  1057. self.opentag('h%s' % level)
  1058. else:
  1059. self.opentag('h%s' % level, {'class':"P-%s" % name })
  1060. self.purgedata()
  1061. def e_text_h(self, tag, attrs):
  1062. """ Headings end
  1063. Side-effect: If there is no title in the metadata, then it is taken
  1064. from the first heading of any level.
  1065. """
  1066. self.writedata()
  1067. level = int(attrs[(TEXTNS,'outline-level')])
  1068. if level > 6: level = 6 # Heading levels go only to 6 in XHTML
  1069. if level < 1: level = 1
  1070. lev = self.headinglevels[1:level+1]
  1071. outline = '.'.join(map(str,lev) )
  1072. heading = ''.join(self.data)
  1073. if self.title == '': self.title = heading
  1074. anchor = self.get_anchor("%s.%s" % ( outline, heading))
  1075. self.opentag('a', {'id': anchor} )
  1076. self.closetag('a', False)
  1077. self.closetag('h%s' % level)
  1078. self.purgedata()
  1079. def s_text_line_break(self, tag, attrs):
  1080. """ Force a line break (<br/>) """
  1081. self.writedata()
  1082. self.emptytag('br')
  1083. self.purgedata()
  1084. def s_text_list(self, tag, attrs):
  1085. """ Start a list (<ul> or <ol>)
  1086. To know which level we're at, we have to count the number
  1087. of <text:list> elements on the tagstack.
  1088. """
  1089. name = attrs.get( (TEXTNS,'style-name') )
  1090. level = self.tagstack.count_tags(tag) + 1
  1091. if name:
  1092. name = name.replace(".","_")
  1093. else:
  1094. # FIXME: If a list is contained in a table cell or text box,
  1095. # the list level must return to 1, even though the table or
  1096. # textbox itself may be nested within another list.
  1097. name = self.tagstack.rfindattr( (TEXTNS,'style-name') )
  1098. list_class = "%s_%d" % (name, level)
  1099. if self.generate_css:
  1100. self.opentag('%s' % self.listtypes.get(list_class,'ul'), {'class': list_class })
  1101. else:
  1102. self.opentag('%s' % self.listtypes.get(list_class,'ul'))
  1103. self.purgedata()
  1104. def e_text_list(self, tag, attrs):
  1105. """ End a list """
  1106. self.writedata()
  1107. name = attrs.get( (TEXTNS,'style-name') )
  1108. level = self.tagstack.count_tags(tag) + 1
  1109. if name:
  1110. name = name.replace(".","_")
  1111. else:
  1112. # FIXME: If a list is contained in a table cell or text box,
  1113. # the list level must return to 1, even though the table or
  1114. # textbox itself may be nested within another list.
  1115. name = self.tagstack.rfindattr( (TEXTNS,'style-name') )
  1116. list_class = "%s_%d" % (name, level)
  1117. self.closetag(self.listtypes.get(list_class,'ul'))
  1118. self.purgedata()
  1119. def s_text_list_item(self, tag, attrs):
  1120. """ Start list item """
  1121. self.opentag('li')
  1122. self.purgedata()
  1123. def e_text_list_item(self, tag, attrs):
  1124. """ End list item """
  1125. self.writedata()
  1126. self.closetag('li')
  1127. self.purgedata()
  1128. def s_text_list_level_style_bullet(self, tag, attrs):
  1129. """ CSS doesn't have the ability to set the glyph
  1130. to a particular character, so we just go through
  1131. the available glyphs
  1132. """
  1133. name = self.tagstack.rfindattr( (STYLENS,'name') )
  1134. level = attrs[(TEXTNS,'level')]
  1135. self.prevstyle = self.currentstyle
  1136. list_class = "%s_%s" % (name, level)
  1137. self.listtypes[list_class] = 'ul'
  1138. self.currentstyle = ".%s_%s" % ( name.replace(".","_"), level)
  1139. self.stylestack.append(self.currentstyle)
  1140. self.styledict[self.currentstyle] = {}
  1141. level = int(level)
  1142. listtype = ("square", "disc", "circle")[level % 3]
  1143. self.styledict[self.currentstyle][('','list-style-type')] = listtype
  1144. def e_text_list_level_style_bullet(self, tag, attrs):
  1145. self.currentstyle = self.prevstyle
  1146. del self.prevstyle
  1147. def s_text_list_level_style_number(self, tag, attrs):
  1148. name = self.tagstack.stackparent()[(STYLENS,'name')]
  1149. level = attrs[(TEXTNS,'level')]
  1150. num_format = attrs.get( (STYLENS,'name'),"1")
  1151. list_class = "%s_%s" % (name, level)
  1152. self.prevstyle = self.currentstyle
  1153. self.currentstyle = ".%s_%s" % ( name.replace(".","_"), level)
  1154. self.listtypes[list_class] = 'ol'
  1155. self.stylestack.append(self.currentstyle)
  1156. self.styledict[self.currentstyle] = {}
  1157. if num_format == "1": listtype = "decimal"
  1158. elif num_format == "I": listtype = "upper-roman"
  1159. elif num_format == "i": listtype = "lower-roman"
  1160. elif num_format == "A": listtype = "upper-alpha"
  1161. elif num_format == "a": listtype = "lower-alpha"
  1162. else: listtype = "decimal"
  1163. self.styledict[self.currentstyle][('','list-style-type')] = listtype
  1164. def e_text_list_level_style_number(self, tag, attrs):
  1165. self.currentstyle = self.prevstyle
  1166. del self.prevstyle
  1167. def s_text_note(self, tag, attrs):
  1168. self.writedata()
  1169. self.purgedata()
  1170. self.currentnote = self.currentnote + 1
  1171. self.notedict[self.currentnote] = {}
  1172. self.notebody = []
  1173. def e_text_note(self, tag, attrs):
  1174. pass
  1175. def collectnote(self,s):
  1176. if s != '':
  1177. self.notebody.append(s)
  1178. def s_text_note_body(self, tag, attrs):
  1179. self._orgwfunc = self._wfunc
  1180. self._wfunc = self.collectnote
  1181. def e_text_note_body(self, tag, attrs):
  1182. self._wfunc = self._orgwfunc
  1183. self.notedict[self.currentnote]['body'] = ''.join(self.notebody)
  1184. self.notebody = ''
  1185. del self._orgwfunc
  1186. def e_text_note_citation(self, tag, attrs):
  1187. mark = ''.join(self.data)
  1188. self.notedict[self.currentnote]['citation'] = mark
  1189. self.opentag('a',{ 'href': "#footnote-%s" % self.currentnote })
  1190. self.opentag('sup')
  1191. # self.writeout( escape(mark) )
  1192. # Since HTML only knows about endnotes, there is too much risk that the
  1193. # marker is reused in the source. Therefore we force numeric markers
  1194. if sys.version_info[0]==3:
  1195. self.writeout(self.currentnote)
  1196. else:
  1197. self.writeout(unicode(self.currentnote))
  1198. self.closetag('sup')
  1199. self.closetag('a')
  1200. def s_text_p(self, tag, attrs):
  1201. """ Paragraph
  1202. """
  1203. htmlattrs = {}
  1204. specialtag = "p"
  1205. c = attrs.get( (TEXTNS,'style-name'), None)
  1206. if c:
  1207. c = c.replace(".","_")
  1208. specialtag = special_styles.get("P-"+c)
  1209. if specialtag is None:
  1210. specialtag = 'p'
  1211. if self.generate_css:
  1212. htmlattrs['class'] = "P-%s" % c
  1213. self.opentag(specialtag, htmlattrs)
  1214. self.purgedata()
  1215. def e_text_p(self, tag, attrs):
  1216. """ End Paragraph
  1217. """
  1218. specialtag = "p"
  1219. c = attrs.get( (TEXTNS,'style-name'), None)
  1220. if c:
  1221. c = c.replace(".","_")
  1222. specialtag = special_styles.get("P-"+c)
  1223. if specialtag is None:
  1224. specialtag = 'p'
  1225. self.writedata()
  1226. self.closetag(specialtag)
  1227. self.purgedata()
  1228. def s_text_s(self, tag, attrs):
  1229. """ Generate a number of spaces. ODF has an element; HTML uses &nbsp;
  1230. We use &#160; so we can send the output through an XML parser if we desire to
  1231. """
  1232. c = attrs.get( (TEXTNS,'c'),"1")
  1233. for x in range(int(c)):
  1234. self.writeout('&#160;')
  1235. def s_text_span(self, tag, attrs):
  1236. """ The <text:span> element matches the <span> element in HTML. It is
  1237. typically used to properties of the text.
  1238. """
  1239. self.writedata()
  1240. c = attrs.get( (TEXTNS,'style-name'), None)
  1241. htmlattrs = {}
  1242. if c:
  1243. c = c.replace(".","_")
  1244. special = special_styles.get("S-"+c)
  1245. if special is None and self.generate_css:
  1246. htmlattrs['class'] = "S-%s" % c
  1247. self.opentag('span', htmlattrs)
  1248. self.purgedata()
  1249. def e_text_span(self, tag, attrs):
  1250. """ End the <text:span> """
  1251. self.writedata()
  1252. self.closetag('span', False)
  1253. self.purgedata()
  1254. def s_text_tab(self, tag, attrs):
  1255. """ Move to the next tabstop. We ignore this in HTML
  1256. """
  1257. self.writedata()
  1258. self.writeout(' ')
  1259. self.purgedata()
  1260. def s_text_x_source(self, tag, attrs):
  1261. """ Various indexes and tables of contents. We ignore those.
  1262. """
  1263. self.writedata()
  1264. self.purgedata()
  1265. self.s_ignorexml(tag, attrs)
  1266. def e_text_x_source(self, tag, attrs):
  1267. """ Various indexes and tables of contents. We ignore those.
  1268. """
  1269. self.writedata()
  1270. self.purgedata()
  1271. #-----------------------------------------------------------------------------
  1272. #
  1273. # Reading the file
  1274. #
  1275. #-----------------------------------------------------------------------------
  1276. def load(self, odffile):
  1277. """
  1278. Loads a document into the parser and parses it.
  1279. The argument can either be a filename or a document in memory.
  1280. @param odffile if the type is unicode string: name of a file; else
  1281. it must be an open file type
  1282. """
  1283. assert(type(odffile)==type(u"") or 'rb' in repr(odffile) or 'BufferedReader' in repr(odffile) or 'BytesIO' in repr(odffile))
  1284. self.lines = []
  1285. self._wfunc = self._wlines
  1286. self.document = load(odffile)
  1287. self._walknode(self.document.topnode)
  1288. def _walknode(self, node):
  1289. if node.nodeType == Node.ELEMENT_NODE:
  1290. self.startElementNS(node.qname, node.tagName, node.attributes)
  1291. for c in node.childNodes:
  1292. self._walknode(c)
  1293. self.endElementNS(node.qname, node.tagName)
  1294. if node.nodeType == Node.TEXT_NODE or node.nodeType == Node.CDATA_SECTION_NODE:
  1295. if sys.version_info[0]==3:
  1296. self.characters(str(node))
  1297. else:
  1298. self.characters(unicode(node))
  1299. def odf2xhtml(self, odffile):
  1300. """
  1301. Load a file and return the XHTML
  1302. @param odffile if the type is unicode string: name of a file; else
  1303. it must be an open file type
  1304. @return XHTML code as a a unicode string
  1305. """
  1306. assert(type(odffile)==type(u"") or 'rb' in repr(odffile) or 'BufferedReader' in repr(odffile) or 'BytesIO' in repr(odffile))
  1307. self.load(odffile)
  1308. result=self.xhtml()
  1309. assert(type(result)==type(u""))
  1310. return result
  1311. def _wlines(self,s):
  1312. if s != '': self.lines.append(s)
  1313. def xhtml(self):
  1314. """ Returns the xhtml
  1315. """
  1316. return ''.join(self.lines)
  1317. def _writecss(self, s):
  1318. if s != '': self._csslines.append(s)
  1319. def _writenothing(self, s):
  1320. pass
  1321. def css(self):
  1322. """ Returns the CSS content """
  1323. self._csslines = []
  1324. self._wfunc = self._writecss
  1325. self.generate_stylesheet()
  1326. res = ''.join(self._csslines)
  1327. self._wfunc = self._wlines
  1328. del self._csslines
  1329. return res
  1330. def save(self, outputfile, addsuffix=False):
  1331. """ Save the HTML under the filename.
  1332. If the filename is '-' then save to stdout
  1333. We have the last style filename in self.stylefilename
  1334. """
  1335. if outputfile == '-':
  1336. outputfp = sys.stdout
  1337. else:
  1338. if addsuffix:
  1339. outputfile = outputfile + ".html"
  1340. outputfp = file(outputfile, "w")
  1341. outputfp.write(self.xhtml().encode('us-ascii','xmlcharrefreplace'))
  1342. outputfp.close()
  1343. class ODF2XHTMLembedded(ODF2XHTML):
  1344. """ The ODF2XHTML parses an ODF file and produces XHTML"""
  1345. def __init__(self, lines, generate_css=True, embedable=False):
  1346. self._resetobject()
  1347. self.lines = lines
  1348. # Tags
  1349. self.generate_css = generate_css
  1350. self.elements = {
  1351. # (DCNS, 'title'): (self.s_processcont, self.e_dc_title),
  1352. # (DCNS, 'language'): (self.s_processcont, self.e_dc_contentlanguage),
  1353. # (DCNS, 'creator'): (self.s_processcont, self.e_dc_metatag),
  1354. # (DCNS, 'description'): (self.s_processcont, self.e_dc_metatag),
  1355. # (DCNS, 'date'): (self.s_processcont, self.e_dc_metatag),
  1356. (DRAWNS, 'frame'): (self.s_draw_frame, self.e_draw_frame),
  1357. (DRAWNS, 'image'): (self.s_draw_image, None),
  1358. (DRAWNS, 'fill-image'): (self.s_draw_fill_image, None),
  1359. (DRAWNS, "layer-set"):(self.s_ignorexml, None),
  1360. (DRAWNS, 'page'): (self.s_draw_page, self.e_draw_page),
  1361. (DRAWNS, 'object'): (self.s_draw_object, None),
  1362. (DRAWNS, 'object-ole'): (self.s_draw_object_ole, None),
  1363. (DRAWNS, 'text-box'): (self.s_draw_textbox, self.e_draw_textbox),
  1364. # (METANS, 'creation-date'):(self.s_processcont, self.e_dc_metatag),
  1365. # (METANS, 'generator'):(self.s_processcont, self.e_dc_metatag),
  1366. # (METANS, 'initial-creator'): (self.s_processcont, self.e_dc_metatag),
  1367. # (METANS, 'keyword'): (self.s_processcont, self.e_dc_metatag),
  1368. (NUMBERNS, "boolean-style"):(self.s_ignorexml, None),
  1369. (NUMBERNS, "currency-style"):(self.s_ignorexml, None),
  1370. (NUMBERNS, "date-style"):(self.s_ignorexml, None),
  1371. (NUMBERNS, "number-style"):(self.s_ignorexml, None),
  1372. (NUMBERNS, "text-style"):(self.s_ignorexml, None),
  1373. # (OFFICENS, "automatic-styles"):(self.s_office_automatic_styles, None),
  1374. # (OFFICENS, "document-content"):(self.s_office_document_content, self.e_office_document_content),
  1375. (OFFICENS, "forms"):(self.s_ignorexml, None),
  1376. # (OFFICENS, "master-styles"):(self.s_office_master_styles, None),
  1377. (OFFICENS, "meta"):(self.s_ignorecont, None),
  1378. # (OFFICENS, "presentation"):(self.s_office_presentation, self.e_office_presentation),
  1379. # (OFFICENS, "spreadsheet"):(self.s_office_spreadsheet, self.e_office_spreadsheet),
  1380. # (OFFICENS, "styles"):(self.s_office_styles, None),
  1381. # (OFFICENS, "text"):(self.s_office_text, self.e_office_text),
  1382. (OFFICENS, "scripts"):(self.s_ignorexml, None),
  1383. (PRESENTATIONNS, "notes"):(self.s_ignorexml, None),
  1384. ## (STYLENS, "default-page-layout"):(self.s_style_default_page_layout, self.e_style_page_layout),
  1385. # (STYLENS, "default-page-layout"):(self.s_ignorexml, None),
  1386. # (STYLENS, "default-style"):(self.s_style_default_style, self.e_style_default_style),
  1387. # (STYLENS, "drawing-page-properties"):(self.s_style_handle_properties, None),
  1388. # (STYLENS, "font-face"):(self.s_style_font_face, None),
  1389. ## (STYLENS, "footer"):(self.s_style_footer, self.e_style_footer),
  1390. ## (STYLENS, "footer-style"):(self.s_style_footer_style, None),
  1391. # (STYLENS, "graphic-properties"):(self.s_style_handle_properties, None),
  1392. # (STYLENS, "handout-master"):(self.s_ignorexml, None),
  1393. ## (STYLENS, "header"):(self.s_style_header, self.e_style_header),
  1394. ## (STYLENS, "header-footer-properties"):(self.s_style_handle_properties, None),
  1395. ## (STYLENS, "header-style"):(self.s_style_header_style, None),
  1396. # (STYLENS, "master-page"):(self.s_style_master_page, None),
  1397. # (STYLENS, "page-layout-properties"):(self.s_style_handle_properties, None),
  1398. ## (STYLENS, "page-layout"):(self.s_style_page_layout, self.e_style_page_layout),
  1399. # (STYLENS, "page-layout"):(self.s_ignorexml, None),
  1400. # (STYLENS, "paragraph-properties"):(self.s_style_handle_properties, None),
  1401. # (STYLENS, "style"):(self.s_style_style, self.e_style_style),
  1402. # (STYLENS, "table-cell-properties"):(self.s_style_handle_properties, None),
  1403. # (STYLENS, "table-column-properties"):(self.s_style_handle_properties, None),
  1404. # (STYLENS, "table-properties"):(self.s_style_handle_properties, None),
  1405. # (STYLENS, "text-properties"):(self.s_style_handle_properties, None),
  1406. (SVGNS, 'desc'): (self.s_ignorexml, None),
  1407. (TABLENS, 'covered-table-cell'): (self.s_ignorexml, None),
  1408. (TABLENS, 'table-cell'): (self.s_table_table_cell, self.e_table_table_cell),
  1409. (TABLENS, 'table-column'): (self.s_table_table_column, None),
  1410. (TABLENS, 'table-row'): (self.s_table_table_row, self.e_table_table_row),
  1411. (TABLENS, 'table'): (self.s_table_table, self.e_table_table),
  1412. (TEXTNS, 'a'): (self.s_text_a, self.e_text_a),
  1413. (TEXTNS, "alphabetical-index-source"):(self.s_text_x_source, self.e_text_x_source),
  1414. (TEXTNS, "bibliography-configuration"):(self.s_ignorexml, None),
  1415. (TEXTNS, "bibliography-source"):(self.s_text_x_source, self.e_text_x_source),
  1416. (TEXTNS, 'h'): (self.s_text_h, self.e_text_h),
  1417. (TEXTNS, "illustration-index-source"):(self.s_text_x_source, self.e_text_x_source),
  1418. (TEXTNS, 'line-break'):(self.s_text_line_break, None),
  1419. (TEXTNS, "linenumbering-configuration"):(self.s_ignorexml, None),
  1420. (TEXTNS, "list"):(self.s_text_list, self.e_text_list),
  1421. (TEXTNS, "list-item"):(self.s_text_list_item, self.e_text_list_item),
  1422. (TEXTNS, "list-level-style-bullet"):(self.s_text_list_level_style_bullet, self.e_text_list_level_style_bullet),
  1423. (TEXTNS, "list-level-style-number"):(self.s_text_list_level_style_number, self.e_text_list_level_style_number),
  1424. (TEXTNS, "list-style"):(None, None),
  1425. (TEXTNS, "note"):(self.s_text_note, None),
  1426. (TEXTNS, "note-body"):(self.s_text_note_body, self.e_text_note_body),
  1427. (TEXTNS, "note-citation"):(None, self.e_text_note_citation),
  1428. (TEXTNS, "notes-configuration"):(self.s_ignorexml, None),
  1429. (TEXTNS, "object-index-source"):(self.s_text_x_source, self.e_text_x_source),
  1430. (TEXTNS, 'p'): (self.s_text_p, self.e_text_p),
  1431. (TEXTNS, 's'): (self.s_text_s, None),
  1432. (TEXTNS, 'span'): (self.s_text_span, self.e_text_span),
  1433. (TEXTNS, 'tab'): (self.s_text_tab, None),
  1434. (TEXTNS, "table-index-source"):(self.s_text_x_source, self.e_text_x_source),
  1435. (TEXTNS, "table-of-content-source"):(self.s_text_x_source, self.e_text_x_source),
  1436. (TEXTNS, "user-index-source"):(self.s_text_x_source, self.e_text_x_source),
  1437. (TEXTNS, "page-number"):(None, None),
  1438. }