1#!/usr/local/bin/python3.8
2# vim:fileencoding=utf-8
3
4
5__license__ = 'GPL v3'
6__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
7
8from lxml.html.builder import TABLE, TR, TD
9
10from calibre.ebooks.docx.block_styles import inherit, read_shd as rs, read_border, binary_property, border_props, ParagraphStyle, border_to_css
11from calibre.ebooks.docx.char_styles import RunStyle
12from polyglot.builtins import iteritems, itervalues
13
14# Read from XML {{{
15read_shd = rs
16edges = ('left', 'top', 'right', 'bottom')
17
18
19def _read_width(elem, get):
20    ans = inherit
21    try:
22        w = int(get(elem, 'w:w'))
23    except (TypeError, ValueError):
24        w = 0
25    typ = get(elem, 'w:type', 'auto')
26    if typ == 'nil':
27        ans = '0'
28    elif typ == 'auto':
29        ans = 'auto'
30    elif typ == 'dxa':
31        ans = '%.3gpt' % (w/20)
32    elif typ == 'pct':
33        ans = '%.3g%%' % (w/50)
34    return ans
35
36
37def read_width(parent, dest, XPath, get):
38    ans = inherit
39    for tblW in XPath('./w:tblW')(parent):
40        ans = _read_width(tblW, get)
41    setattr(dest, 'width', ans)
42
43
44def read_cell_width(parent, dest, XPath, get):
45    ans = inherit
46    for tblW in XPath('./w:tcW')(parent):
47        ans = _read_width(tblW, get)
48    setattr(dest, 'width', ans)
49
50
51def read_padding(parent, dest, XPath, get):
52    name = 'tblCellMar' if parent.tag.endswith('}tblPr') else 'tcMar'
53    ans = {x:inherit for x in edges}
54    for mar in XPath('./w:%s' % name)(parent):
55        for x in edges:
56            for edge in XPath('./w:%s' % x)(mar):
57                ans[x] = _read_width(edge, get)
58    for x in edges:
59        setattr(dest, 'cell_padding_%s' % x, ans[x])
60
61
62def read_justification(parent, dest, XPath, get):
63    left = right = inherit
64    for jc in XPath('./w:jc[@w:val]')(parent):
65        val = get(jc, 'w:val')
66        if not val:
67            continue
68        if val == 'left':
69            right = 'auto'
70        elif val == 'right':
71            left = 'auto'
72        elif val == 'center':
73            left = right = 'auto'
74    setattr(dest, 'margin_left', left)
75    setattr(dest, 'margin_right', right)
76
77
78def read_spacing(parent, dest, XPath, get):
79    ans = inherit
80    for cs in XPath('./w:tblCellSpacing')(parent):
81        ans = _read_width(cs, get)
82    setattr(dest, 'spacing', ans)
83
84
85def read_float(parent, dest, XPath, get):
86    ans = inherit
87    for x in XPath('./w:tblpPr')(parent):
88        ans = {k.rpartition('}')[-1]: v for k, v in iteritems(x.attrib)}
89    setattr(dest, 'float', ans)
90
91
92def read_indent(parent, dest, XPath, get):
93    ans = inherit
94    for cs in XPath('./w:tblInd')(parent):
95        ans = _read_width(cs, get)
96    setattr(dest, 'indent', ans)
97
98
99border_edges = ('left', 'top', 'right', 'bottom', 'insideH', 'insideV')
100
101
102def read_borders(parent, dest, XPath, get):
103    name = 'tblBorders' if parent.tag.endswith('}tblPr') else 'tcBorders'
104    read_border(parent, dest, XPath, get, border_edges, name)
105
106
107def read_height(parent, dest, XPath, get):
108    ans = inherit
109    for rh in XPath('./w:trHeight')(parent):
110        rule = get(rh, 'w:hRule', 'auto')
111        if rule in {'auto', 'atLeast', 'exact'}:
112            val = get(rh, 'w:val')
113            ans = (rule, val)
114    setattr(dest, 'height', ans)
115
116
117def read_vertical_align(parent, dest, XPath, get):
118    ans = inherit
119    for va in XPath('./w:vAlign')(parent):
120        val = get(va, 'w:val')
121        ans = {'center': 'middle', 'top': 'top', 'bottom': 'bottom'}.get(val, 'middle')
122    setattr(dest, 'vertical_align', ans)
123
124
125def read_col_span(parent, dest, XPath, get):
126    ans = inherit
127    for gs in XPath('./w:gridSpan')(parent):
128        try:
129            ans = int(get(gs, 'w:val'))
130        except (TypeError, ValueError):
131            continue
132    setattr(dest, 'col_span', ans)
133
134
135def read_merge(parent, dest, XPath, get):
136    for x in ('hMerge', 'vMerge'):
137        ans = inherit
138        for m in XPath('./w:%s' % x)(parent):
139            ans = get(m, 'w:val', 'continue')
140        setattr(dest, x, ans)
141
142
143def read_band_size(parent, dest, XPath, get):
144    for x in ('Col', 'Row'):
145        ans = 1
146        for y in XPath('./w:tblStyle%sBandSize' % x)(parent):
147            try:
148                ans = int(get(y, 'w:val'))
149            except (TypeError, ValueError):
150                continue
151        setattr(dest, '%s_band_size' % x.lower(), ans)
152
153
154def read_look(parent, dest, XPath, get):
155    ans = 0
156    for x in XPath('./w:tblLook')(parent):
157        try:
158            ans = int(get(x, 'w:val'), 16)
159        except (ValueError, TypeError):
160            continue
161    setattr(dest, 'look', ans)
162
163# }}}
164
165
166def clone(style):
167    if style is None:
168        return None
169    try:
170        ans = type(style)(style.namespace)
171    except TypeError:
172        return None
173    ans.update(style)
174    return ans
175
176
177class Style:
178
179    is_bidi = False
180
181    def update(self, other):
182        for prop in self.all_properties:
183            nval = getattr(other, prop)
184            if nval is not inherit:
185                setattr(self, prop, nval)
186
187    def apply_bidi(self):
188        self.is_bidi = True
189
190    def convert_spacing(self):
191        ans = {}
192        if self.spacing is not inherit:
193            if self.spacing in {'auto', '0'}:
194                ans['border-collapse'] = 'collapse'
195            else:
196                ans['border-collapse'] = 'separate'
197                ans['border-spacing'] = self.spacing
198        return ans
199
200    def convert_border(self):
201        c = {}
202        for x in edges:
203            border_to_css(x, self, c)
204            val = getattr(self, 'padding_%s' % x)
205            if val is not inherit:
206                c['padding-%s' % x] = '%.3gpt' % val
207        if self.is_bidi:
208            for a in ('padding-%s', 'border-%s-style', 'border-%s-color', 'border-%s-width'):
209                l, r = c.get(a % 'left'), c.get(a % 'right')
210                if l is not None:
211                    c[a % 'right'] = l
212                if r is not None:
213                    c[a % 'left'] = r
214        return c
215
216
217class RowStyle(Style):
218
219    all_properties = ('height', 'cantSplit', 'hidden', 'spacing',)
220
221    def __init__(self, namespace, trPr=None):
222        self.namespace = namespace
223        if trPr is None:
224            for p in self.all_properties:
225                setattr(self, p, inherit)
226        else:
227            for p in ('hidden', 'cantSplit'):
228                setattr(self, p, binary_property(trPr, p, namespace.XPath, namespace.get))
229            for p in ('spacing', 'height'):
230                f = globals()['read_%s' % p]
231                f(trPr, self, namespace.XPath, namespace.get)
232        self._css = None
233
234    @property
235    def css(self):
236        if self._css is None:
237            c = self._css = {}
238            if self.hidden is True:
239                c['display'] = 'none'
240            if self.cantSplit is True:
241                c['page-break-inside'] = 'avoid'
242            if self.height is not inherit:
243                rule, val = self.height
244                if rule != 'auto':
245                    try:
246                        c['min-height' if rule == 'atLeast' else 'height'] = '%.3gpt' % (int(val)/20)
247                    except (ValueError, TypeError):
248                        pass
249            c.update(self.convert_spacing())
250        return self._css
251
252
253class CellStyle(Style):
254
255    all_properties = ('background_color', 'cell_padding_left', 'cell_padding_right', 'cell_padding_top',
256        'cell_padding_bottom', 'width', 'vertical_align', 'col_span', 'vMerge', 'hMerge', 'row_span',
257    ) + tuple(k % edge for edge in border_edges for k in border_props)
258
259    def __init__(self, namespace, tcPr=None):
260        self.namespace = namespace
261        if tcPr is None:
262            for p in self.all_properties:
263                setattr(self, p, inherit)
264        else:
265            for x in ('borders', 'shd', 'padding', 'cell_width', 'vertical_align', 'col_span', 'merge'):
266                f = globals()['read_%s' % x]
267                f(tcPr, self, namespace.XPath, namespace.get)
268            self.row_span = inherit
269        self._css = None
270
271    @property
272    def css(self):
273        if self._css is None:
274            self._css = c = {}
275            if self.background_color is not inherit:
276                c['background-color'] = self.background_color
277            if self.width not in (inherit, 'auto'):
278                c['width'] = self.width
279            c['vertical-align'] = 'top' if self.vertical_align is inherit else self.vertical_align
280            for x in edges:
281                val = getattr(self, 'cell_padding_%s' % x)
282                if val not in (inherit, 'auto'):
283                    c['padding-%s' % x] =  val
284                elif val is inherit and x in {'left', 'right'}:
285                    c['padding-%s' % x] = '%.3gpt' % (115/20)
286            # In Word, tables are apparently rendered with some default top and
287            # bottom padding irrespective of the cellMargin values. Simulate
288            # that here.
289            for x in ('top', 'bottom'):
290                if c.get('padding-%s' % x, '0pt') == '0pt':
291                    c['padding-%s' % x] = '0.5ex'
292            c.update(self.convert_border())
293
294        return self._css
295
296
297class TableStyle(Style):
298
299    all_properties = (
300        'width', 'float', 'cell_padding_left', 'cell_padding_right', 'cell_padding_top',
301        'cell_padding_bottom', 'margin_left', 'margin_right', 'background_color',
302        'spacing', 'indent', 'overrides', 'col_band_size', 'row_band_size', 'look', 'bidi',
303    ) + tuple(k % edge for edge in border_edges for k in border_props)
304
305    def __init__(self, namespace, tblPr=None):
306        self.namespace = namespace
307        if tblPr is None:
308            for p in self.all_properties:
309                setattr(self, p, inherit)
310        else:
311            self.overrides = inherit
312            self.bidi = binary_property(tblPr, 'bidiVisual', namespace.XPath, namespace.get)
313            for x in ('width', 'float', 'padding', 'shd', 'justification', 'spacing', 'indent', 'borders', 'band_size', 'look'):
314                f = globals()['read_%s' % x]
315                f(tblPr, self, self.namespace.XPath, self.namespace.get)
316            parent = tblPr.getparent()
317            if self.namespace.is_tag(parent, 'w:style'):
318                self.overrides = {}
319                for tblStylePr in self.namespace.XPath('./w:tblStylePr[@w:type]')(parent):
320                    otype = self.namespace.get(tblStylePr, 'w:type')
321                    orides = self.overrides[otype] = {}
322                    for tblPr in self.namespace.XPath('./w:tblPr')(tblStylePr):
323                        orides['table'] = TableStyle(self.namespace, tblPr)
324                    for trPr in self.namespace.XPath('./w:trPr')(tblStylePr):
325                        orides['row'] = RowStyle(self.namespace, trPr)
326                    for tcPr in self.namespace.XPath('./w:tcPr')(tblStylePr):
327                        orides['cell'] = CellStyle(self.namespace, tcPr)
328                    for pPr in self.namespace.XPath('./w:pPr')(tblStylePr):
329                        orides['para'] = ParagraphStyle(self.namespace, pPr)
330                    for rPr in self.namespace.XPath('./w:rPr')(tblStylePr):
331                        orides['run'] = RunStyle(self.namespace, rPr)
332        self._css = None
333
334    def resolve_based_on(self, parent):
335        for p in self.all_properties:
336            val = getattr(self, p)
337            if val is inherit:
338                setattr(self, p, getattr(parent, p))
339
340    @property
341    def css(self):
342        if self._css is None:
343            c = self._css = {}
344            if self.width not in (inherit, 'auto'):
345                c['width'] = self.width
346            for x in ('background_color', 'margin_left', 'margin_right'):
347                val = getattr(self, x)
348                if val is not inherit:
349                    c[x.replace('_', '-')] = val
350            if self.indent not in (inherit, 'auto') and self.margin_left != 'auto':
351                c['margin-left'] = self.indent
352            if self.float is not inherit:
353                for x in ('left', 'top', 'right', 'bottom'):
354                    val = self.float.get('%sFromText' % x, 0)
355                    try:
356                        val = '%.3gpt' % (int(val) / 20)
357                    except (ValueError, TypeError):
358                        val = '0'
359                    c['margin-%s' % x] = val
360                if 'tblpXSpec' in self.float:
361                    c['float'] = 'right' if self.float['tblpXSpec'] in {'right', 'outside'} else 'left'
362                else:
363                    page = self.page
364                    page_width = page.width - page.margin_left - page.margin_right
365                    try:
366                        x = int(self.float['tblpX']) / 20
367                    except (KeyError, ValueError, TypeError):
368                        x = 0
369                    c['float'] = 'left' if (x/page_width) < 0.65 else 'right'
370            c.update(self.convert_spacing())
371            if 'border-collapse' not in c:
372                c['border-collapse'] = 'collapse'
373            c.update(self.convert_border())
374
375        return self._css
376
377
378class Table:
379
380    def __init__(self, namespace, tbl, styles, para_map, is_sub_table=False):
381        self.namespace = namespace
382        self.tbl = tbl
383        self.styles = styles
384        self.is_sub_table = is_sub_table
385
386        # Read Table Style
387        style = {'table':TableStyle(self.namespace)}
388        for tblPr in self.namespace.XPath('./w:tblPr')(tbl):
389            for ts in self.namespace.XPath('./w:tblStyle[@w:val]')(tblPr):
390                style_id = self.namespace.get(ts, 'w:val')
391                s = styles.get(style_id)
392                if s is not None:
393                    if s.table_style is not None:
394                        style['table'].update(s.table_style)
395                    if s.paragraph_style is not None:
396                        if 'paragraph' in style:
397                            style['paragraph'].update(s.paragraph_style)
398                        else:
399                            style['paragraph'] = s.paragraph_style
400                    if s.character_style is not None:
401                        if 'run' in style:
402                            style['run'].update(s.character_style)
403                        else:
404                            style['run'] = s.character_style
405            style['table'].update(TableStyle(self.namespace, tblPr))
406        self.table_style, self.paragraph_style = style['table'], style.get('paragraph', None)
407        self.run_style = style.get('run', None)
408        self.overrides = self.table_style.overrides
409        if self.overrides is inherit:
410            self.overrides = {}
411        if 'wholeTable' in self.overrides and 'table' in self.overrides['wholeTable']:
412            self.table_style.update(self.overrides['wholeTable']['table'])
413
414        self.style_map = {}
415        self.paragraphs = []
416        self.cell_map = []
417
418        rows = self.namespace.XPath('./w:tr')(tbl)
419        for r, tr in enumerate(rows):
420            overrides = self.get_overrides(r, None, len(rows), None)
421            self.resolve_row_style(tr, overrides)
422            cells = self.namespace.XPath('./w:tc')(tr)
423            self.cell_map.append([])
424            for c, tc in enumerate(cells):
425                overrides = self.get_overrides(r, c, len(rows), len(cells))
426                self.resolve_cell_style(tc, overrides, r, c, len(rows), len(cells))
427                self.cell_map[-1].append(tc)
428                for p in self.namespace.XPath('./w:p')(tc):
429                    para_map[p] = self
430                    self.paragraphs.append(p)
431                    self.resolve_para_style(p, overrides)
432
433        self.handle_merged_cells()
434        self.sub_tables = {x:Table(namespace, x, styles, para_map, is_sub_table=True) for x in self.namespace.XPath('./w:tr/w:tc/w:tbl')(tbl)}
435
436    @property
437    def bidi(self):
438        return self.table_style.bidi is True
439
440    def override_allowed(self, name):
441        'Check if the named override is allowed by the tblLook element'
442        if name.endswith('Cell') or name == 'wholeTable':
443            return True
444        look = self.table_style.look
445        if (look & 0x0020 and name == 'firstRow') or (look & 0x0040 and name == 'lastRow') or \
446           (look & 0x0080 and name == 'firstCol') or (look & 0x0100 and name == 'lastCol'):
447            return True
448        if name.startswith('band'):
449            if name.endswith('Horz'):
450                return not bool(look & 0x0200)
451            if name.endswith('Vert'):
452                return not bool(look & 0x0400)
453        return False
454
455    def get_overrides(self, r, c, num_of_rows, num_of_cols_in_row):
456        'List of possible overrides for the given para'
457        overrides = ['wholeTable']
458
459        def divisor(m, n):
460            return (m - (m % n)) // n
461        if c is not None:
462            odd_column_band = (divisor(c, self.table_style.col_band_size) % 2) == 1
463            overrides.append('band%dVert' % (1 if odd_column_band else 2))
464        odd_row_band = (divisor(r, self.table_style.row_band_size) % 2) == 1
465        overrides.append('band%dHorz' % (1 if odd_row_band else 2))
466
467        # According to the OOXML spec columns should have higher override
468        # priority than rows, but Word seems to do it the other way around.
469        if c is not None:
470            if c == 0:
471                overrides.append('firstCol')
472            if c >= num_of_cols_in_row - 1:
473                overrides.append('lastCol')
474        if r == 0:
475            overrides.append('firstRow')
476        if r >= num_of_rows - 1:
477            overrides.append('lastRow')
478        if c is not None:
479            if r == 0:
480                if c == 0:
481                    overrides.append('nwCell')
482                if c == num_of_cols_in_row - 1:
483                    overrides.append('neCell')
484            if r == num_of_rows - 1:
485                if c == 0:
486                    overrides.append('swCell')
487                if c == num_of_cols_in_row - 1:
488                    overrides.append('seCell')
489        return tuple(filter(self.override_allowed, overrides))
490
491    def resolve_row_style(self, tr, overrides):
492        rs = RowStyle(self.namespace)
493        for o in overrides:
494            if o in self.overrides:
495                ovr = self.overrides[o]
496                ors = ovr.get('row', None)
497                if ors is not None:
498                    rs.update(ors)
499
500        for trPr in self.namespace.XPath('./w:trPr')(tr):
501            rs.update(RowStyle(self.namespace, trPr))
502        if self.bidi:
503            rs.apply_bidi()
504        self.style_map[tr] = rs
505
506    def resolve_cell_style(self, tc, overrides, row, col, rows, cols_in_row):
507        cs = CellStyle(self.namespace)
508        for o in overrides:
509            if o in self.overrides:
510                ovr = self.overrides[o]
511                ors = ovr.get('cell', None)
512                if ors is not None:
513                    cs.update(ors)
514
515        for tcPr in self.namespace.XPath('./w:tcPr')(tc):
516            cs.update(CellStyle(self.namespace, tcPr))
517
518        for x in edges:
519            p = 'cell_padding_%s' % x
520            val = getattr(cs, p)
521            if val is inherit:
522                setattr(cs, p, getattr(self.table_style, p))
523
524            is_inside_edge = (
525                (x == 'left' and col > 0) or
526                (x == 'top' and row > 0) or
527                (x == 'right' and col < cols_in_row - 1) or
528                (x == 'bottom' and row < rows -1)
529            )
530            inside_edge = ('insideH' if x in {'top', 'bottom'} else 'insideV') if is_inside_edge else None
531            for prop in border_props:
532                if not prop.startswith('border'):
533                    continue
534                eprop = prop % x
535                iprop = (prop % inside_edge) if inside_edge else None
536                val = getattr(cs, eprop)
537                if val is inherit and iprop is not None:
538                    # Use the insideX borders if the main cell borders are not
539                    # specified
540                    val = getattr(cs, iprop)
541                    if val is inherit:
542                        val = getattr(self.table_style, iprop)
543                if not is_inside_edge and val == 'none':
544                    # Cell borders must override table borders even when the
545                    # table border is not null and the cell border is null.
546                    val = 'hidden'
547                setattr(cs, eprop, val)
548
549        if self.bidi:
550            cs.apply_bidi()
551        self.style_map[tc] = cs
552
553    def resolve_para_style(self, p, overrides):
554        text_styles = [clone(self.paragraph_style), clone(self.run_style)]
555
556        for o in overrides:
557            if o in self.overrides:
558                ovr = self.overrides[o]
559                for i, name in enumerate(('para', 'run')):
560                    ops = ovr.get(name, None)
561                    if ops is not None:
562                        if text_styles[i] is None:
563                            text_styles[i] = ops
564                        else:
565                            text_styles[i].update(ops)
566        self.style_map[p] = text_styles
567
568    def handle_merged_cells(self):
569        if not self.cell_map:
570            return
571        # Handle vMerge
572        max_col_num = max(len(r) for r in self.cell_map)
573        for c in range(max_col_num):
574            cells = [row[c] if c < len(row) else None for row in self.cell_map]
575            runs = [[]]
576            for cell in cells:
577                try:
578                    s = self.style_map[cell]
579                except KeyError:  # cell is None
580                    s = CellStyle(self.namespace)
581                if s.vMerge == 'restart':
582                    runs.append([cell])
583                elif s.vMerge == 'continue':
584                    runs[-1].append(cell)
585                else:
586                    runs.append([])
587            for run in runs:
588                if len(run) > 1:
589                    self.style_map[run[0]].row_span = len(run)
590                    for tc in run[1:]:
591                        tc.getparent().remove(tc)
592
593        # Handle hMerge
594        for cells in self.cell_map:
595            runs = [[]]
596            for cell in cells:
597                try:
598                    s = self.style_map[cell]
599                except KeyError:  # cell is None
600                    s = CellStyle(self.namespace)
601                if s.col_span is not inherit:
602                    runs.append([])
603                    continue
604                if s.hMerge == 'restart':
605                    runs.append([cell])
606                elif s.hMerge == 'continue':
607                    runs[-1].append(cell)
608                else:
609                    runs.append([])
610
611            for run in runs:
612                if len(run) > 1:
613                    self.style_map[run[0]].col_span = len(run)
614                    for tc in run[1:]:
615                        tc.getparent().remove(tc)
616
617    def __iter__(self):
618        yield from self.paragraphs
619        for t in itervalues(self.sub_tables):
620            yield from t
621
622    def apply_markup(self, rmap, page, parent=None):
623        table = TABLE('\n\t\t')
624        if self.bidi:
625            table.set('dir', 'rtl')
626        self.table_style.page = page
627        style_map = {}
628        if parent is None:
629            try:
630                first_para = rmap[next(iter(self))]
631            except StopIteration:
632                return
633            parent = first_para.getparent()
634            idx = parent.index(first_para)
635            parent.insert(idx, table)
636        else:
637            parent.append(table)
638        for row in self.namespace.XPath('./w:tr')(self.tbl):
639            tr = TR('\n\t\t\t')
640            style_map[tr] = self.style_map[row]
641            tr.tail = '\n\t\t'
642            table.append(tr)
643            for tc in self.namespace.XPath('./w:tc')(row):
644                td = TD()
645                style_map[td] = s = self.style_map[tc]
646                if s.col_span is not inherit:
647                    td.set('colspan', str(s.col_span))
648                if s.row_span is not inherit:
649                    td.set('rowspan', str(s.row_span))
650                td.tail = '\n\t\t\t'
651                tr.append(td)
652                for x in self.namespace.XPath('./w:p|./w:tbl')(tc):
653                    if x.tag.endswith('}p'):
654                        td.append(rmap[x])
655                    else:
656                        self.sub_tables[x].apply_markup(rmap, page, parent=td)
657            if len(tr):
658                tr[-1].tail = '\n\t\t'
659        if len(table):
660            table[-1].tail = '\n\t'
661
662        table_style = self.table_style.css
663        if table_style:
664            table.set('class', self.styles.register(table_style, 'table'))
665        for elem, style in iteritems(style_map):
666            css = style.css
667            if css:
668                elem.set('class', self.styles.register(css, elem.tag))
669
670
671class Tables:
672
673    def __init__(self, namespace):
674        self.tables = []
675        self.para_map = {}
676        self.sub_tables = set()
677        self.namespace = namespace
678
679    def register(self, tbl, styles):
680        if tbl in self.sub_tables:
681            return
682        self.tables.append(Table(self.namespace, tbl, styles, self.para_map))
683        self.sub_tables |= set(self.tables[-1].sub_tables)
684
685    def apply_markup(self, object_map, page_map):
686        rmap = {v:k for k, v in iteritems(object_map)}
687        for table in self.tables:
688            table.apply_markup(rmap, page_map[table.tbl])
689
690    def para_style(self, p):
691        table = self.para_map.get(p, None)
692        if table is not None:
693            return table.style_map.get(p, (None, None))[0]
694
695    def run_style(self, p):
696        table = self.para_map.get(p, None)
697        if table is not None:
698            return table.style_map.get(p, (None, None))[1]
699