mdhtml2docx 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/CHANGELOG.md +3 -0
- {mdhtml2docx-0.1.2/mdhtml2docx.egg-info → mdhtml2docx-0.1.3}/PKG-INFO +10 -10
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/README.md +9 -9
- mdhtml2docx-0.1.3/mdhtml2docx/__init__.py +6 -0
- mdhtml2docx-0.1.2/mdhtml2docx/convert.py → mdhtml2docx-0.1.3/mdhtml2docx/mdhtml2docx.py +17 -13
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/wml.py +1 -1
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3/mdhtml2docx.egg-info}/PKG-INFO +10 -10
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/SOURCES.txt +1 -1
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/tests/test_convert.py +39 -39
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/tests/test_word.py +3 -3
- mdhtml2docx-0.1.2/mdhtml2docx/__init__.py +0 -5
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/LICENSE +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/MANIFEST.in +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/asvocab.py +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/hilite.py +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/SOURCES.md +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-chart.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-chartDrawing.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-diagram.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-lockedCanvas.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-main.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-picture.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-wordprocessingDrawing.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-commonSimpleTypes.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-customXmlSchemaProperties.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-math.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-relationshipReference.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/wml.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/xml.xsd +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/styles.py +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/templates/reference.docx +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/validate.py +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/word.py +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/word.sdef +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/dependency_links.txt +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/requires.txt +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/top_level.txt +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/pyproject.toml +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/setup.cfg +0 -0
- {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/tests/test_validate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdhtml2docx
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Convert MDHTML to Word docx files
|
|
5
5
|
Author-email: Jeremy Howard <j@fast.ai>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -30,21 +30,21 @@ MDHTML accepts the full HTML vocabulary. This exporter supports the portable ele
|
|
|
30
30
|
## Usage
|
|
31
31
|
|
|
32
32
|
```python
|
|
33
|
-
from mdhtml import
|
|
34
|
-
from mdhtml2docx import
|
|
33
|
+
from mdhtml import md2mdhtml
|
|
34
|
+
from mdhtml2docx import mdhtml2docx
|
|
35
35
|
|
|
36
|
-
html =
|
|
37
|
-
warnings =
|
|
36
|
+
html = md2mdhtml(markdown_text)
|
|
37
|
+
warnings = mdhtml2docx(html, 'out.docx')
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
`
|
|
40
|
+
`mdhtml2docx` takes an MDHTML string, writes a docx, and returns a list of warning strings. It parses strings with `mdhtml.mdhtml2dom`, so normal HTML5 repair applies and XML well-formedness is irrelevant. It also accepts an existing mutable fast5ever DOM:
|
|
41
41
|
|
|
42
42
|
```python
|
|
43
|
-
from mdhtml import
|
|
43
|
+
from mdhtml import mdhtml2dom
|
|
44
44
|
|
|
45
|
-
document =
|
|
45
|
+
document = mdhtml2dom(html)
|
|
46
46
|
document.children[0].attrs['custom-style'] = 'Contract Title'
|
|
47
|
-
warnings =
|
|
47
|
+
warnings = mdhtml2docx(document, 'out.docx')
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Body-position text and phrasing content become implicit Word paragraphs. Whitespace between blocks remains inert. HTML templates remain inert and are not rendered.
|
|
@@ -89,7 +89,7 @@ The word before the number comes from the reference's type, the id up to its fir
|
|
|
89
89
|
|
|
90
90
|
Number fields need numbered headings. If your reference docx already numbers its heading styles (most firm templates do), nothing more is required, and the converter leaves that numbering alone. Otherwise pass `number_headings='legal'` for 1. / (a) / (i) numbering or `'decimal'` for 1 / 1.1 / 1.1.1 (the names index `styles.SCHEMES`), or pass your own scheme as a `{lvlText: numFmt}` dict, one entry per heading level. A reference-list entry ending in `.xml` is a third route: a raw file of `w:style`, `w:abstractNum`, and `w:num` elements contributing styles and numbering together, with ids remapped to avoid collisions.
|
|
91
91
|
|
|
92
|
-
Figures and captioned tables number themselves with SEQ fields: a figure renders as its image plus a "Figure 1: caption" paragraph below (caption style), a table caption as "Table 1: caption" above the table, both live. When the element has an id, the label-and-number span is bookmarked, so `[@fig-plot]` inserts a live "Figure 1" (no extra prefix word; the label is part of the bookmarked text) and `[-@tbl-stages]` the bare number via a second number-only bookmark. `fig` and `tbl` are built-in reftypes alongside `sec`, and their label words come from the same table. Mixed-type groups render each item with its own singular prefix ("Figure 1 and Table 2"); same-type groups pluralize once. Reference targets must be things that get bookmarks - headings, paragraphs, figures, and tables with ids - and a ref to anything else is an error at
|
|
92
|
+
Figures and captioned tables number themselves with SEQ fields: a figure renders as its image plus a "Figure 1: caption" paragraph below (caption style), a table caption as "Table 1: caption" above the table, both live. When the element has an id, the label-and-number span is bookmarked, so `[@fig-plot]` inserts a live "Figure 1" (no extra prefix word; the label is part of the bookmarked text) and `[-@tbl-stages]` the bare number via a second number-only bookmark. `fig` and `tbl` are built-in reftypes alongside `sec`, and their label words come from the same table. Mixed-type groups render each item with its own singular prefix ("Figure 1 and Table 2"); same-type groups pluralize once. Reference targets must be things that get bookmarks - headings, paragraphs, figures, and tables with ids - and a ref to anything else is an error at conversion time.
|
|
93
93
|
|
|
94
94
|
## Validation
|
|
95
95
|
|
|
@@ -9,21 +9,21 @@ MDHTML accepts the full HTML vocabulary. This exporter supports the portable ele
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
11
|
```python
|
|
12
|
-
from mdhtml import
|
|
13
|
-
from mdhtml2docx import
|
|
12
|
+
from mdhtml import md2mdhtml
|
|
13
|
+
from mdhtml2docx import mdhtml2docx
|
|
14
14
|
|
|
15
|
-
html =
|
|
16
|
-
warnings =
|
|
15
|
+
html = md2mdhtml(markdown_text)
|
|
16
|
+
warnings = mdhtml2docx(html, 'out.docx')
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
`
|
|
19
|
+
`mdhtml2docx` takes an MDHTML string, writes a docx, and returns a list of warning strings. It parses strings with `mdhtml.mdhtml2dom`, so normal HTML5 repair applies and XML well-formedness is irrelevant. It also accepts an existing mutable fast5ever DOM:
|
|
20
20
|
|
|
21
21
|
```python
|
|
22
|
-
from mdhtml import
|
|
22
|
+
from mdhtml import mdhtml2dom
|
|
23
23
|
|
|
24
|
-
document =
|
|
24
|
+
document = mdhtml2dom(html)
|
|
25
25
|
document.children[0].attrs['custom-style'] = 'Contract Title'
|
|
26
|
-
warnings =
|
|
26
|
+
warnings = mdhtml2docx(document, 'out.docx')
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Body-position text and phrasing content become implicit Word paragraphs. Whitespace between blocks remains inert. HTML templates remain inert and are not rendered.
|
|
@@ -68,7 +68,7 @@ The word before the number comes from the reference's type, the id up to its fir
|
|
|
68
68
|
|
|
69
69
|
Number fields need numbered headings. If your reference docx already numbers its heading styles (most firm templates do), nothing more is required, and the converter leaves that numbering alone. Otherwise pass `number_headings='legal'` for 1. / (a) / (i) numbering or `'decimal'` for 1 / 1.1 / 1.1.1 (the names index `styles.SCHEMES`), or pass your own scheme as a `{lvlText: numFmt}` dict, one entry per heading level. A reference-list entry ending in `.xml` is a third route: a raw file of `w:style`, `w:abstractNum`, and `w:num` elements contributing styles and numbering together, with ids remapped to avoid collisions.
|
|
70
70
|
|
|
71
|
-
Figures and captioned tables number themselves with SEQ fields: a figure renders as its image plus a "Figure 1: caption" paragraph below (caption style), a table caption as "Table 1: caption" above the table, both live. When the element has an id, the label-and-number span is bookmarked, so `[@fig-plot]` inserts a live "Figure 1" (no extra prefix word; the label is part of the bookmarked text) and `[-@tbl-stages]` the bare number via a second number-only bookmark. `fig` and `tbl` are built-in reftypes alongside `sec`, and their label words come from the same table. Mixed-type groups render each item with its own singular prefix ("Figure 1 and Table 2"); same-type groups pluralize once. Reference targets must be things that get bookmarks - headings, paragraphs, figures, and tables with ids - and a ref to anything else is an error at
|
|
71
|
+
Figures and captioned tables number themselves with SEQ fields: a figure renders as its image plus a "Figure 1: caption" paragraph below (caption style), a table caption as "Table 1: caption" above the table, both live. When the element has an id, the label-and-number span is bookmarked, so `[@fig-plot]` inserts a live "Figure 1" (no extra prefix word; the label is part of the bookmarked text) and `[-@tbl-stages]` the bare number via a second number-only bookmark. `fig` and `tbl` are built-in reftypes alongside `sec`, and their label words come from the same table. Mixed-type groups render each item with its own singular prefix ("Figure 1 and Table 2"); same-type groups pluralize once. Reference targets must be things that get bookmarks - headings, paragraphs, figures, and tables with ids - and a ref to anything else is an error at conversion time.
|
|
72
72
|
|
|
73
73
|
## Validation
|
|
74
74
|
|
|
@@ -9,7 +9,7 @@ from copy import deepcopy
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from fast5ever import Comment, Element, Node, Text
|
|
11
11
|
from lxml import etree
|
|
12
|
-
from mdhtml import
|
|
12
|
+
from mdhtml import mdhtml2dom
|
|
13
13
|
from mdhtml.export import REFTYPES, SCHEMES, decode_raw, tmpl_node, group_plan, ref_tokens, ref_variant, target_kind, Resolver
|
|
14
14
|
from .styles import STYLE_MAP, style_id, theme_styles
|
|
15
15
|
from .styles import ref_path as _refpath
|
|
@@ -17,7 +17,7 @@ from .wml import *
|
|
|
17
17
|
from .wml import qn
|
|
18
18
|
from .hilite import segments, tokenize
|
|
19
19
|
|
|
20
|
-
__all__ = ['
|
|
20
|
+
__all__ = ['mdhtml2docx', 'mustache_fields']
|
|
21
21
|
|
|
22
22
|
def _sid(key): return style_id(STYLE_MAP[key])
|
|
23
23
|
|
|
@@ -38,7 +38,7 @@ def parse_frag(src):
|
|
|
38
38
|
"Parse an MDHTML body fragment, or return an existing mutable fragment"
|
|
39
39
|
if isinstance(src, Node): return src
|
|
40
40
|
if not isinstance(src, str): raise TypeError('input must be an MDHTML string or fast5ever node')
|
|
41
|
-
return
|
|
41
|
+
return mdhtml2dom(src)
|
|
42
42
|
|
|
43
43
|
class Converter:
|
|
44
44
|
def __init__(self, reference=None, base=None, reftypes=None, number_headings=None, tmpl=None):
|
|
@@ -604,13 +604,17 @@ class Converter:
|
|
|
604
604
|
BIND_ID = '{8E2C9A44-7D31-4E5B-9C0D-1A6F2B3C4D5E}' # fixed datastore id, so builds are reproducible
|
|
605
605
|
|
|
606
606
|
def tmpl_runs(self, el, fmt, form):
|
|
607
|
-
"""Template-
|
|
608
|
-
|
|
609
|
-
callable with the
|
|
607
|
+
"""Template-instruction runs. Non-value Mustache operations are converter policy: literal
|
|
608
|
+
marker runs, so an unfilled form shows its structure. Value operations go through the `tmpl`
|
|
609
|
+
callable with the semantic node dict (see `mdhtml.export.tmpl_node`): str is a literal text
|
|
610
610
|
run, ('field', instr) a live field, ('control', name) an interactive plain-text content
|
|
611
611
|
control, ('bound', name) a data-bound one, None dropped"""
|
|
612
|
+
if 'data-op' not in el.attrs: return []
|
|
612
613
|
node = tmpl_node(el, form)
|
|
613
|
-
|
|
614
|
+
action = node['op'].rsplit(':', 1)[-1]
|
|
615
|
+
if action != 'value':
|
|
616
|
+
marker = dict(section='#', inverted='^', end='/').get(action, '')
|
|
617
|
+
return self.text_runs(f'«{marker}{node["value"]}»', fmt)
|
|
614
618
|
if self.tmpl is None: return []
|
|
615
619
|
res = self.tmpl(node)
|
|
616
620
|
if res is None: return []
|
|
@@ -618,7 +622,7 @@ class Converter:
|
|
|
618
622
|
kind, val = res
|
|
619
623
|
if kind == 'field':
|
|
620
624
|
self.has_fields = True
|
|
621
|
-
return [E('w:fldSimple', {'w:instr': f' {val.strip()} '}, E('w:r', self.rpr(fmt), E('w:t', f'«{node["
|
|
625
|
+
return [E('w:fldSimple', {'w:instr': f' {val.strip()} '}, E('w:r', self.rpr(fmt), E('w:t', f'«{node["value"]}»')))]
|
|
622
626
|
if kind in ('control', 'bound'):
|
|
623
627
|
self.has_controls = True
|
|
624
628
|
sdtpr = E('w:sdtPr', E('w:alias', {'w:val': val}), E('w:tag', {'w:val': val}), E('w:showingPlcHdr'))
|
|
@@ -903,11 +907,11 @@ class Converter:
|
|
|
903
907
|
|
|
904
908
|
def mustache_fields(node):
|
|
905
909
|
"Template variables as live Word `MERGEFIELD`s (markers never reach `tmpl`: the converter shows them literally)"
|
|
906
|
-
return 'field', f'MERGEFIELD {node["
|
|
910
|
+
return 'field', f'MERGEFIELD {node["value"]}'
|
|
907
911
|
|
|
908
912
|
|
|
909
913
|
|
|
910
|
-
def
|
|
914
|
+
def mdhtml2docx(mdhtml, dest, reference=None, base=None, reftypes=None, number_headings=None, tmpl=None):
|
|
911
915
|
"""Convert an MDHTML string or mutable fast5ever DOM to a docx file at `dest`; returns warnings.
|
|
912
916
|
`reference` is a reference docx path, or a list of them: the first supplies the whole archive
|
|
913
917
|
(default, or when None: the built-in template), later entries contribute styles only, later-wins -
|
|
@@ -917,9 +921,9 @@ def convert(mdhtml, dest, reference=None, base=None, reftypes=None, number_headi
|
|
|
917
921
|
resolve against `base` ('.'). Cross-references (`data-ref` anchors from Markdown `[@sec-x]`) become
|
|
918
922
|
live REF fields; `reftypes` maps type tokens to (singular, plural) prefix words beyond the built-in
|
|
919
923
|
`sec`, and `number_headings` (a styles.SCHEMES name such as 'legal', or a {lvlText: numFmt} dict, one entry per heading level)
|
|
920
|
-
numbers the headings via a multilevel list so `\\w` fields resolve. Template
|
|
921
|
-
unless `tmpl` is given
|
|
922
|
-
`
|
|
924
|
+
numbers the headings via a multilevel list so `\\w` fields resolve. Template value instructions are dropped
|
|
925
|
+
unless `tmpl` is given; other operations remain visible markers. `tmpl` is a callable taking the semantic instruction dict
|
|
926
|
+
(`mdhtml.export.tmpl_node`: `op`, `value`, `form`) and returning a str for a literal text run,
|
|
923
927
|
`('field', instr)` for a live field, `('control', name)` for an interactive plain-text content
|
|
924
928
|
control, `('bound', name)` for a content control data-bound to a shared per-variable XML node
|
|
925
929
|
(same-name controls stay in sync as one is filled), or None to drop; range markers never reach `tmpl` and render as literal «body» runs - `mustache_fields` here
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""WordprocessingML construction helpers: namespaces, a qualified-name element builder, unit
|
|
2
2
|
conversions, and the colwidths track-list parser. Child-element order inside w:pPr/w:rPr/w:tcPr
|
|
3
3
|
etc follows the ECMA-376 content models; builders here emit children in the order callers pass
|
|
4
|
-
them, so callers are responsible for schema order (the
|
|
4
|
+
them, so callers are responsible for schema order (the mdhtml2docx module's helpers encode it)."""
|
|
5
5
|
import re, struct
|
|
6
6
|
from xml.sax.saxutils import escape
|
|
7
7
|
from lxml import etree
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdhtml2docx
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Convert MDHTML to Word docx files
|
|
5
5
|
Author-email: Jeremy Howard <j@fast.ai>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -30,21 +30,21 @@ MDHTML accepts the full HTML vocabulary. This exporter supports the portable ele
|
|
|
30
30
|
## Usage
|
|
31
31
|
|
|
32
32
|
```python
|
|
33
|
-
from mdhtml import
|
|
34
|
-
from mdhtml2docx import
|
|
33
|
+
from mdhtml import md2mdhtml
|
|
34
|
+
from mdhtml2docx import mdhtml2docx
|
|
35
35
|
|
|
36
|
-
html =
|
|
37
|
-
warnings =
|
|
36
|
+
html = md2mdhtml(markdown_text)
|
|
37
|
+
warnings = mdhtml2docx(html, 'out.docx')
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
`
|
|
40
|
+
`mdhtml2docx` takes an MDHTML string, writes a docx, and returns a list of warning strings. It parses strings with `mdhtml.mdhtml2dom`, so normal HTML5 repair applies and XML well-formedness is irrelevant. It also accepts an existing mutable fast5ever DOM:
|
|
41
41
|
|
|
42
42
|
```python
|
|
43
|
-
from mdhtml import
|
|
43
|
+
from mdhtml import mdhtml2dom
|
|
44
44
|
|
|
45
|
-
document =
|
|
45
|
+
document = mdhtml2dom(html)
|
|
46
46
|
document.children[0].attrs['custom-style'] = 'Contract Title'
|
|
47
|
-
warnings =
|
|
47
|
+
warnings = mdhtml2docx(document, 'out.docx')
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Body-position text and phrasing content become implicit Word paragraphs. Whitespace between blocks remains inert. HTML templates remain inert and are not rendered.
|
|
@@ -89,7 +89,7 @@ The word before the number comes from the reference's type, the id up to its fir
|
|
|
89
89
|
|
|
90
90
|
Number fields need numbered headings. If your reference docx already numbers its heading styles (most firm templates do), nothing more is required, and the converter leaves that numbering alone. Otherwise pass `number_headings='legal'` for 1. / (a) / (i) numbering or `'decimal'` for 1 / 1.1 / 1.1.1 (the names index `styles.SCHEMES`), or pass your own scheme as a `{lvlText: numFmt}` dict, one entry per heading level. A reference-list entry ending in `.xml` is a third route: a raw file of `w:style`, `w:abstractNum`, and `w:num` elements contributing styles and numbering together, with ids remapped to avoid collisions.
|
|
91
91
|
|
|
92
|
-
Figures and captioned tables number themselves with SEQ fields: a figure renders as its image plus a "Figure 1: caption" paragraph below (caption style), a table caption as "Table 1: caption" above the table, both live. When the element has an id, the label-and-number span is bookmarked, so `[@fig-plot]` inserts a live "Figure 1" (no extra prefix word; the label is part of the bookmarked text) and `[-@tbl-stages]` the bare number via a second number-only bookmark. `fig` and `tbl` are built-in reftypes alongside `sec`, and their label words come from the same table. Mixed-type groups render each item with its own singular prefix ("Figure 1 and Table 2"); same-type groups pluralize once. Reference targets must be things that get bookmarks - headings, paragraphs, figures, and tables with ids - and a ref to anything else is an error at
|
|
92
|
+
Figures and captioned tables number themselves with SEQ fields: a figure renders as its image plus a "Figure 1: caption" paragraph below (caption style), a table caption as "Table 1: caption" above the table, both live. When the element has an id, the label-and-number span is bookmarked, so `[@fig-plot]` inserts a live "Figure 1" (no extra prefix word; the label is part of the bookmarked text) and `[-@tbl-stages]` the bare number via a second number-only bookmark. `fig` and `tbl` are built-in reftypes alongside `sec`, and their label words come from the same table. Mixed-type groups render each item with its own singular prefix ("Figure 1 and Table 2"); same-type groups pluralize once. Reference targets must be things that get bookmarks - headings, paragraphs, figures, and tables with ids - and a ref to anything else is an error at conversion time.
|
|
93
93
|
|
|
94
94
|
## Validation
|
|
95
95
|
|
|
@@ -4,10 +4,10 @@ from pathlib import Path
|
|
|
4
4
|
from fastcore.test import test_eq as teq, test as tt, test_fail as tfail
|
|
5
5
|
from fastcore.utils import in_
|
|
6
6
|
|
|
7
|
-
from mdhtml import DASHES, replacements,
|
|
7
|
+
from mdhtml import DASHES, replacements, mdhtml2dom, md2mdhtml
|
|
8
8
|
from mdhtml.mustache import MUSTACHE
|
|
9
9
|
from mdhtml.tools import SAMPLE_MD, sample_md
|
|
10
|
-
from mdhtml2docx.
|
|
10
|
+
from mdhtml2docx.mdhtml2docx import mdhtml2docx, mustache_fields
|
|
11
11
|
from mdhtml2docx.validate import fast_checks
|
|
12
12
|
|
|
13
13
|
|
|
@@ -20,7 +20,7 @@ def pandoc(path, to='markdown'):
|
|
|
20
20
|
|
|
21
21
|
def test_skeleton(tmp_path):
|
|
22
22
|
out = tmp_path/'t.docx'
|
|
23
|
-
|
|
23
|
+
mdhtml2docx('<p>Hello <em>world</em> with <strong>bold</strong> and <strong><em>both</em></strong>.</p>\n'
|
|
24
24
|
'<p>Second para.</p>', out)
|
|
25
25
|
teq(fast_checks(out), 'valid')
|
|
26
26
|
md = pandoc(out)
|
|
@@ -33,28 +33,28 @@ def test_skeleton(tmp_path):
|
|
|
33
33
|
|
|
34
34
|
def test_dash_replacements(tmp_path):
|
|
35
35
|
out = tmp_path/'t.docx'
|
|
36
|
-
|
|
36
|
+
mdhtml2docx(md2mdhtml('scores 3--5 -- nice... `a--b`', callbacks={'text': replacements(*DASHES)}), out)
|
|
37
37
|
doc = zipfile.ZipFile(out).read('word/document.xml').decode()
|
|
38
38
|
for s in ('3–5', ' – nice… ', 'a--b'): tt(s, doc, in_)
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def test_html5_fragment_and_mutable_dom_input(tmp_path):
|
|
42
42
|
out = tmp_path/'t.docx'
|
|
43
|
-
dom =
|
|
43
|
+
dom = mdhtml2dom('<p>Before <div>middle</div> after.</p><span zoop:33="x">tail</span>'
|
|
44
44
|
'<!-- this is a -- comment --><input type="date"><template><p>hidden</p></template>')
|
|
45
45
|
dom.children[1].attrs['data-kind'] = 'changed'
|
|
46
|
-
warns =
|
|
46
|
+
warns = mdhtml2docx(dom, out)
|
|
47
47
|
teq(warns, ["unhandled inline <input type='date'>; dropped"])
|
|
48
48
|
md = pandoc(out)
|
|
49
49
|
for s in ('Before', 'middle', 'after.', 'tail'): tt(s, md, in_)
|
|
50
50
|
assert 'hidden' not in md
|
|
51
|
-
|
|
51
|
+
mdhtml2docx('<template><p>still hidden</p></template>', out)
|
|
52
52
|
assert '<w:p>' not in zipfile.ZipFile(out).read('word/document.xml').decode()
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def test_basic_blocks(tmp_path):
|
|
56
56
|
out = tmp_path/'t.docx'
|
|
57
|
-
warns =
|
|
57
|
+
warns = mdhtml2docx(
|
|
58
58
|
'<h1 id="top">Title</h1>\n<h2>Sub <em>title</em></h2>\n'
|
|
59
59
|
'<p>Call <code>f(x)</code> or see <a href="https://fast.ai/">fast.ai</a> '
|
|
60
60
|
'and <a href="#top">the title</a>.</p>\n'
|
|
@@ -72,7 +72,7 @@ def test_basic_blocks(tmp_path):
|
|
|
72
72
|
|
|
73
73
|
def test_lists(tmp_path):
|
|
74
74
|
out = tmp_path/'t.docx'
|
|
75
|
-
warns =
|
|
75
|
+
warns = mdhtml2docx(
|
|
76
76
|
'<ul>\n<li>one</li>\n<li>two\n<ul>\n<li>deep</li>\n</ul>\n</li>\n</ul>\n'
|
|
77
77
|
'<ol>\n<li>first</li>\n<li>second</li>\n</ol>\n'
|
|
78
78
|
'<ol start="5">\n<li>fifth</li>\n</ol>\n'
|
|
@@ -91,7 +91,7 @@ def test_lists(tmp_path):
|
|
|
91
91
|
|
|
92
92
|
def test_tables(tmp_path):
|
|
93
93
|
out = tmp_path/'t.docx'
|
|
94
|
-
warns =
|
|
94
|
+
warns = mdhtml2docx(
|
|
95
95
|
'<table colwidths="10em 2fr 1fr">\n<thead>\n'
|
|
96
96
|
'<tr><th align="left">Feature</th><th align="center">Status</th><th align="right">Notes</th></tr>\n'
|
|
97
97
|
'</thead>\n<tbody>\n'
|
|
@@ -116,7 +116,7 @@ def test_tables(tmp_path):
|
|
|
116
116
|
def test_more_features(tmp_path):
|
|
117
117
|
out = tmp_path/'t.docx'
|
|
118
118
|
img = Path(__file__).parent/'fixtures'/'tiny.png'
|
|
119
|
-
warns =
|
|
119
|
+
warns = mdhtml2docx(
|
|
120
120
|
'<p>H<sub>2</sub>O, E=mc<sup>2</sup>, <del>gone</del>, <mark>hot</mark>, <u>under</u>.</p>\n'
|
|
121
121
|
'<hr>\n'
|
|
122
122
|
'<dl>\n<dt>term</dt>\n<dd>definition here</dd>\n</dl>\n'
|
|
@@ -162,7 +162,7 @@ def test_imgsize():
|
|
|
162
162
|
|
|
163
163
|
def test_math(tmp_path):
|
|
164
164
|
out = tmp_path/'t.docx'
|
|
165
|
-
warns =
|
|
165
|
+
warns = mdhtml2docx('<p>Euler: <span class="math inline">e^{i\\pi} + 1 = 0</span> inline.</p>\n'
|
|
166
166
|
'<div class="math display">E = mc^2</div>', out)
|
|
167
167
|
teq(warns, [])
|
|
168
168
|
teq(fast_checks(out), 'valid')
|
|
@@ -180,7 +180,7 @@ def test_sample(tmp_path):
|
|
|
180
180
|
from lxml import etree
|
|
181
181
|
from mdhtml2docx.styles import STYLE_MAP, style_id
|
|
182
182
|
out = tmp_path/'sample.docx'
|
|
183
|
-
warns =
|
|
183
|
+
warns = mdhtml2docx(md2mdhtml(sample_md(), callbacks={'text': replacements(*DASHES)}, implicit_figures=True), out, base=SAMPLE_MD.parent, number_headings='legal')
|
|
184
184
|
teq(warns, [])
|
|
185
185
|
teq(fast_checks(out), 'valid')
|
|
186
186
|
W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
|
|
@@ -207,7 +207,7 @@ def test_sample(tmp_path):
|
|
|
207
207
|
def test_code_highlight(tmp_path):
|
|
208
208
|
"fastpylight scopes: language-classed blocks get Hl* character styles from the theme ref; text still round-trips"
|
|
209
209
|
out = tmp_path/'t.docx'
|
|
210
|
-
warns =
|
|
210
|
+
warns = mdhtml2docx('<pre><code class="language-python">def f(x):\n return "hi"\n</code></pre>\n'
|
|
211
211
|
'<pre><code>no language here</code></pre>', out)
|
|
212
212
|
teq(warns, [])
|
|
213
213
|
teq(fast_checks(out), 'valid')
|
|
@@ -228,7 +228,7 @@ def test_theme_refs(tmp_path):
|
|
|
228
228
|
from mdhtml2docx.styles import ref_path, theme_ref
|
|
229
229
|
mdhtml = '<pre><code class="language-python">def f(x):\n return x\n</code></pre>'
|
|
230
230
|
out = tmp_path/'t.docx'
|
|
231
|
-
|
|
231
|
+
mdhtml2docx(mdhtml, out, reference=[ref_path(), 'dracula'])
|
|
232
232
|
teq(fast_checks(out), 'valid')
|
|
233
233
|
styles = zipfile.ZipFile(out).read('word/styles.xml').decode()
|
|
234
234
|
tt('w:styleId="HlKeyword"', styles, in_)
|
|
@@ -240,12 +240,12 @@ def test_theme_refs(tmp_path):
|
|
|
240
240
|
ref = theme_ref('dracula', tmp_path/'dracula.docx')
|
|
241
241
|
teq(fast_checks(ref), 'valid')
|
|
242
242
|
out2 = tmp_path/'t2.docx'
|
|
243
|
-
|
|
243
|
+
mdhtml2docx(mdhtml, out2, reference=[ref_path(), ref])
|
|
244
244
|
ids = lambda p: sorted(s.split('"')[1] for s in zipfile.ZipFile(p).read('word/styles.xml').decode().split('w:styleId=')[1:])
|
|
245
245
|
teq(ids(out2), ids(out))
|
|
246
246
|
# single custom reference (no theme entry) = plain code
|
|
247
247
|
out3 = tmp_path/'t3.docx'
|
|
248
|
-
|
|
248
|
+
mdhtml2docx(mdhtml, out3, reference=ref_path())
|
|
249
249
|
assert 'HlKeyword' not in zipfile.ZipFile(out3).read('word/document.xml').decode()
|
|
250
250
|
|
|
251
251
|
|
|
@@ -262,7 +262,7 @@ def test_segments_scopes():
|
|
|
262
262
|
def test_raw_docx(tmp_path):
|
|
263
263
|
out = tmp_path/'t.docx'
|
|
264
264
|
b64 = base64.b64encode(b'<w:r><w:t>B64</w:t></w:r>').decode()
|
|
265
|
-
warns =
|
|
265
|
+
warns = mdhtml2docx(
|
|
266
266
|
'<p>before</p>\n'
|
|
267
267
|
'<script type="application/vnd.mdhtml.raw" data-format="docx"><w:p><w:r><w:br w:type="page"/></w:r></w:p></script>\n'
|
|
268
268
|
'<p>a <script type="application/vnd.mdhtml.raw" data-format="docx" data-encoding="html"><w:r><w:t>RAW</w:t></w:r></script> b</p>\n'
|
|
@@ -275,17 +275,17 @@ def test_raw_docx(tmp_path):
|
|
|
275
275
|
md = pandoc(out)
|
|
276
276
|
for s in ('a RAW b', 'B64'): tt(s, md, in_)
|
|
277
277
|
assert 'newpage' not in doc # unrecognized format dropped
|
|
278
|
-
warns =
|
|
278
|
+
warns = mdhtml2docx('<script type="application/vnd.mdhtml.raw" data-format="docx"><w:oops></script>', out)
|
|
279
279
|
teq(len(warns), 1)
|
|
280
280
|
tt('malformed', warns[0], in_)
|
|
281
|
-
warns =
|
|
281
|
+
warns = mdhtml2docx('<script type="application/vnd.mdhtml.raw" data-format="docx" data-encoding="base64">!!!</script>', out)
|
|
282
282
|
teq(len(warns), 1)
|
|
283
283
|
tt('malformed base64', warns[0], in_)
|
|
284
284
|
|
|
285
285
|
|
|
286
286
|
def test_xrefs(tmp_path):
|
|
287
287
|
out = tmp_path/'t.docx'
|
|
288
|
-
warns =
|
|
288
|
+
warns = mdhtml2docx(
|
|
289
289
|
'<h1 id="sec-intro">Intro</h1>\n<h2 id="sec-pay">Payment terms</h2>\n'
|
|
290
290
|
'<p>See <a href="#sec-pay" data-ref=""></a> and <a href="#sec-intro" data-ref="bare"></a>, '
|
|
291
291
|
'per <a href="#sec-pay" data-ref="">Clause</a>, also '
|
|
@@ -302,15 +302,15 @@ def test_xrefs(tmp_path):
|
|
|
302
302
|
for s in ('lowerLetter', '(%2)', 'Heading1'): tt(s, num, in_)
|
|
303
303
|
tt('updateFields', zipfile.ZipFile(out).read('word/settings.xml').decode(), in_)
|
|
304
304
|
tt('w:numPr', zipfile.ZipFile(out).read('word/styles.xml').decode(), in_)
|
|
305
|
-
tfail(lambda:
|
|
306
|
-
tfail(lambda:
|
|
305
|
+
tfail(lambda: mdhtml2docx('<p><a href="#nope" data-ref=""></a></p>', out), contains='#nope')
|
|
306
|
+
tfail(lambda: mdhtml2docx('<p id="z-a"><a href="#z-a" data-ref=""></a></p>', out),
|
|
307
307
|
contains="reference type 'z'")
|
|
308
|
-
tfail(lambda:
|
|
308
|
+
tfail(lambda: mdhtml2docx('<p><a href="#sec-pay" data-ref="page text"></a></p>', out), contains='conflicting data-ref')
|
|
309
309
|
|
|
310
310
|
|
|
311
311
|
def test_text_targets(tmp_path):
|
|
312
312
|
out = tmp_path/'t.docx'
|
|
313
|
-
warns =
|
|
313
|
+
warns = mdhtml2docx(
|
|
314
314
|
'<h1 id="sec-d">Defs</h1>\n'
|
|
315
315
|
'<dl><dt id="def-term"><strong>"Term"</strong></dt><dt id="def-ap">Agreement Period</dt>'
|
|
316
316
|
'<dd>the deal period</dd></dl>\n'
|
|
@@ -326,9 +326,9 @@ def test_text_targets(tmp_path):
|
|
|
326
326
|
def test_xrefs_numbered_reference_doc(tmp_path):
|
|
327
327
|
"A numbered docx (made by us) as reference: numbering merges with list numbering, no reinjection"
|
|
328
328
|
ref = tmp_path/'ref.docx'
|
|
329
|
-
|
|
329
|
+
mdhtml2docx('<h1 id="a">A</h1>', ref, number_headings='legal')
|
|
330
330
|
out = tmp_path/'t.docx'
|
|
331
|
-
warns =
|
|
331
|
+
warns = mdhtml2docx('<h1 id="sec-a">A</h1>\n<ul>\n<li>one</li>\n</ul>\n'
|
|
332
332
|
'<p>See <a href="#sec-a" data-ref=""></a>.</p>', out, reference=ref)
|
|
333
333
|
teq(warns, [])
|
|
334
334
|
teq(fast_checks(out), 'valid')
|
|
@@ -351,7 +351,7 @@ def test_xml_contributor(tmp_path):
|
|
|
351
351
|
'<w:pPr><w:numPr><w:ilvl w:val="0"/><w:numId w:val="1"/></w:numPr></w:pPr>'
|
|
352
352
|
'</w:style></w:styles>')
|
|
353
353
|
out = tmp_path/'t.docx'
|
|
354
|
-
warns =
|
|
354
|
+
warns = mdhtml2docx('<p custom-style="Fancy">Hi</p>\n<ul>\n<li>one</li>\n</ul>', out,
|
|
355
355
|
reference=[None, xml])
|
|
356
356
|
teq(warns, [])
|
|
357
357
|
teq(fast_checks(out), 'valid')
|
|
@@ -365,7 +365,7 @@ def test_caption_refs(tmp_path):
|
|
|
365
365
|
"Figures and captioned tables: SEQ-numbered captions, label+number bookmarks, fig/tbl refs"
|
|
366
366
|
out = tmp_path/'t.docx'
|
|
367
367
|
img = Path(__file__).parent/'fixtures'/'tiny.png'
|
|
368
|
-
warns =
|
|
368
|
+
warns = mdhtml2docx(
|
|
369
369
|
f'<figure id="fig-plot" class="wide"><img src="{img}" alt=""><figcaption>A plot</figcaption></figure>\n'
|
|
370
370
|
'<table id="tbl-r"><caption>Results <em>now</em></caption><thead><tr><th>a</th></tr></thead>'
|
|
371
371
|
'<tbody><tr><td>1</td></tr></tbody></table>\n'
|
|
@@ -382,17 +382,17 @@ def test_caption_refs(tmp_path):
|
|
|
382
382
|
md = pandoc(out)
|
|
383
383
|
tt('A plot', md, in_)
|
|
384
384
|
# a ref to an id that never becomes a bookmark is an error, not a dud field
|
|
385
|
-
tfail(lambda:
|
|
385
|
+
tfail(lambda: mdhtml2docx('<div id="d-x"><p>hi</p></div><p><a href="#d-x" data-ref=""></a></p>', out),
|
|
386
386
|
contains='#d-x')
|
|
387
387
|
|
|
388
388
|
|
|
389
389
|
def test_template_tokens(tmp_path):
|
|
390
390
|
out = tmp_path/'t.docx'
|
|
391
|
-
src =
|
|
392
|
-
|
|
391
|
+
src = md2mdhtml('Pay {{sal}} to {{name}}.\n\n{{#opt}}\n\nGranted.\n\n{{/opt}}\n', templates=MUSTACHE)
|
|
392
|
+
mdhtml2docx(src, out) # default: vars dropped, markers literal
|
|
393
393
|
doc = zipfile.ZipFile(out).read('word/document.xml').decode()
|
|
394
394
|
assert 'sal' not in doc and '{{' not in doc and '«#opt»' in doc # vars dropped; markers stay visible
|
|
395
|
-
warns =
|
|
395
|
+
warns = mdhtml2docx(src, out, tmpl=mustache_fields)
|
|
396
396
|
doc = zipfile.ZipFile(out).read('word/document.xml').decode()
|
|
397
397
|
assert 'MERGEFIELD sal' in doc and 'MERGEFIELD name' in doc
|
|
398
398
|
assert '«#opt»' in doc and '«/opt»' in doc # markers: converter-rendered literals, own paragraphs
|
|
@@ -401,7 +401,7 @@ def test_template_tokens(tmp_path):
|
|
|
401
401
|
|
|
402
402
|
def test_table_custom_style(tmp_path):
|
|
403
403
|
out = tmp_path/'t.docx'
|
|
404
|
-
|
|
404
|
+
mdhtml2docx(md2mdhtml('| A |\n|---|\n| b |\n{: custom-style="Borderless Table"}\n\n| C |\n|---|\n| d |\n'), out)
|
|
405
405
|
doc = zipfile.ZipFile(out).read('word/document.xml').decode()
|
|
406
406
|
tt('<w:tblStyle w:val="BorderlessTable"/>', doc, in_) # styled table picks the reference style
|
|
407
407
|
tt('<w:tblStyle w:val="TableGrid"/>', doc, in_) # plain table keeps the default
|
|
@@ -411,9 +411,9 @@ def test_table_custom_style(tmp_path):
|
|
|
411
411
|
|
|
412
412
|
|
|
413
413
|
def test_template_controls(tmp_path):
|
|
414
|
-
def controls(node): return 'control', node['
|
|
414
|
+
def controls(node): return 'control', node['value']
|
|
415
415
|
out = tmp_path/'t.docx'
|
|
416
|
-
|
|
416
|
+
mdhtml2docx(md2mdhtml('Pay {{sal}} to {{name}}.\n\n{{#opt}}\n', templates=MUSTACHE), out, tmpl=controls)
|
|
417
417
|
doc = zipfile.ZipFile(out).read('word/document.xml').decode()
|
|
418
418
|
assert doc.count('<w:sdt>') == 2 and 'w:val="sal"' in doc and 'w:val="name"' in doc
|
|
419
419
|
assert doc.count('<w:showingPlcHdr/>') == 2 and 'w:val="PlaceholderText"' in doc
|
|
@@ -424,9 +424,9 @@ def test_template_controls(tmp_path):
|
|
|
424
424
|
|
|
425
425
|
|
|
426
426
|
def test_template_bound(tmp_path):
|
|
427
|
-
def bound(node): return 'bound', node['
|
|
427
|
+
def bound(node): return 'bound', node['value']
|
|
428
428
|
out = tmp_path/'t.docx'
|
|
429
|
-
|
|
429
|
+
mdhtml2docx(md2mdhtml('Pay {{sal}} to {{sal}} and {{name}}.\n', templates=MUSTACHE), out, tmpl=bound)
|
|
430
430
|
z = zipfile.ZipFile(out)
|
|
431
431
|
doc = z.read('word/document.xml').decode()
|
|
432
432
|
assert doc.count('<w:dataBinding ') == 3 and doc.count('/ns0:fields[1]/ns0:sal[1]') == 2
|
|
@@ -439,7 +439,7 @@ def test_template_bound(tmp_path):
|
|
|
439
439
|
|
|
440
440
|
def test_details_degrades_to_bold_label(tmp_path):
|
|
441
441
|
out = tmp_path/'t.docx'
|
|
442
|
-
warns =
|
|
442
|
+
warns = mdhtml2docx(md2mdhtml('::: {.details .tool-usage-details}\n## the label\n\nbody text\n:::\n'), out)
|
|
443
443
|
assert not warns
|
|
444
444
|
md = pandoc(out)
|
|
445
445
|
tt('**the label**', md, in_)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from mdhtml import DASHES, replacements,
|
|
4
|
+
from mdhtml import DASHES, replacements, md2mdhtml
|
|
5
5
|
from mdhtml.tools import SAMPLE_MD, sample_md
|
|
6
|
-
from mdhtml2docx import
|
|
6
|
+
from mdhtml2docx import mdhtml2docx
|
|
7
7
|
|
|
8
8
|
DATA = Path(__file__).parent.parent/'_data'
|
|
9
9
|
|
|
@@ -12,6 +12,6 @@ pytestmark = [pytest.mark.slow, pytest.mark.checkout]
|
|
|
12
12
|
|
|
13
13
|
def test_sample():
|
|
14
14
|
docx = DATA/'sample.docx'
|
|
15
|
-
warns =
|
|
15
|
+
warns = mdhtml2docx(md2mdhtml(sample_md(), callbacks={'text': replacements(*DASHES)}, implicit_figures=True), docx, base=SAMPLE_MD.parent, number_headings='legal')
|
|
16
16
|
assert not warns
|
|
17
17
|
assert docx.stat().st_size
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-customXmlSchemaProperties.xsd
RENAMED
|
File without changes
|
|
File without changes
|
{mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-relationshipReference.xsd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|