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.
Files changed (40) hide show
  1. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/CHANGELOG.md +3 -0
  2. {mdhtml2docx-0.1.2/mdhtml2docx.egg-info → mdhtml2docx-0.1.3}/PKG-INFO +10 -10
  3. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/README.md +9 -9
  4. mdhtml2docx-0.1.3/mdhtml2docx/__init__.py +6 -0
  5. mdhtml2docx-0.1.2/mdhtml2docx/convert.py → mdhtml2docx-0.1.3/mdhtml2docx/mdhtml2docx.py +17 -13
  6. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/wml.py +1 -1
  7. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3/mdhtml2docx.egg-info}/PKG-INFO +10 -10
  8. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/SOURCES.txt +1 -1
  9. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/tests/test_convert.py +39 -39
  10. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/tests/test_word.py +3 -3
  11. mdhtml2docx-0.1.2/mdhtml2docx/__init__.py +0 -5
  12. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/LICENSE +0 -0
  13. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/MANIFEST.in +0 -0
  14. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/asvocab.py +0 -0
  15. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/hilite.py +0 -0
  16. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/SOURCES.md +0 -0
  17. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-chart.xsd +0 -0
  18. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-chartDrawing.xsd +0 -0
  19. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-diagram.xsd +0 -0
  20. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-lockedCanvas.xsd +0 -0
  21. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-main.xsd +0 -0
  22. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-picture.xsd +0 -0
  23. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/dml-wordprocessingDrawing.xsd +0 -0
  24. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-commonSimpleTypes.xsd +0 -0
  25. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-customXmlSchemaProperties.xsd +0 -0
  26. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-math.xsd +0 -0
  27. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/shared-relationshipReference.xsd +0 -0
  28. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/wml.xsd +0 -0
  29. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/schemas/xml.xsd +0 -0
  30. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/styles.py +0 -0
  31. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/templates/reference.docx +0 -0
  32. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/validate.py +0 -0
  33. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/word.py +0 -0
  34. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx/word.sdef +0 -0
  35. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/dependency_links.txt +0 -0
  36. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/requires.txt +0 -0
  37. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/mdhtml2docx.egg-info/top_level.txt +0 -0
  38. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/pyproject.toml +0 -0
  39. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/setup.cfg +0 -0
  40. {mdhtml2docx-0.1.2 → mdhtml2docx-0.1.3}/tests/test_validate.py +0 -0
@@ -1,5 +1,8 @@
1
1
  <!-- do not remove -->
2
2
 
3
+ ## 0.1.3
4
+
5
+
3
6
  ## 0.1.2
4
7
 
5
8
  ### New Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mdhtml2docx
3
- Version: 0.1.2
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 to_mdhtml
34
- from mdhtml2docx import convert
33
+ from mdhtml import md2mdhtml
34
+ from mdhtml2docx import mdhtml2docx
35
35
 
36
- html = to_mdhtml(markdown_text)
37
- warnings = convert(html, 'out.docx')
36
+ html = md2mdhtml(markdown_text)
37
+ warnings = mdhtml2docx(html, 'out.docx')
38
38
  ```
39
39
 
40
- `convert` takes an MDHTML string, writes a docx, and returns a list of warning strings. It parses strings with `mdhtml.parse_mdhtml`, so normal HTML5 repair applies and XML well-formedness is irrelevant. It also accepts an existing mutable fast5ever DOM:
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 parse_mdhtml
43
+ from mdhtml import mdhtml2dom
44
44
 
45
- document = parse_mdhtml(html)
45
+ document = mdhtml2dom(html)
46
46
  document.children[0].attrs['custom-style'] = 'Contract Title'
47
- warnings = convert(document, 'out.docx')
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 convert time.
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 to_mdhtml
13
- from mdhtml2docx import convert
12
+ from mdhtml import md2mdhtml
13
+ from mdhtml2docx import mdhtml2docx
14
14
 
15
- html = to_mdhtml(markdown_text)
16
- warnings = convert(html, 'out.docx')
15
+ html = md2mdhtml(markdown_text)
16
+ warnings = mdhtml2docx(html, 'out.docx')
17
17
  ```
18
18
 
19
- `convert` takes an MDHTML string, writes a docx, and returns a list of warning strings. It parses strings with `mdhtml.parse_mdhtml`, so normal HTML5 repair applies and XML well-formedness is irrelevant. It also accepts an existing mutable fast5ever DOM:
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 parse_mdhtml
22
+ from mdhtml import mdhtml2dom
23
23
 
24
- document = parse_mdhtml(html)
24
+ document = mdhtml2dom(html)
25
25
  document.children[0].attrs['custom-style'] = 'Contract Title'
26
- warnings = convert(document, 'out.docx')
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 convert time.
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
 
@@ -0,0 +1,6 @@
1
+ __version__ = "0.1.3"
2
+
3
+
4
+
5
+
6
+ from .mdhtml2docx import mdhtml2docx
@@ -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 parse_mdhtml
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__ = ['convert', 'mustache_fields']
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 parse_mdhtml(src)
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-token runs. Range markers and unknown tokens are converter policy: literal
608
- `«body»` runs, so an unfilled form shows its markers. Var tokens go through the `tmpl`
609
- callable with the token node dict (see `mdhtml.export.tmpl_node`): str is a literal text
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
- if node["kind"] != "var": return self.text_runs(f'«{node["body"].strip()}»', fmt)
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["name"]}»')))]
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["name"]}'
910
+ return 'field', f'MERGEFIELD {node["value"]}'
907
911
 
908
912
 
909
913
 
910
- def convert(mdhtml, dest, reference=None, base=None, reftypes=None, number_headings=None, tmpl=None):
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 tokens are dropped
921
- unless `tmpl` is given: a callable taking the token node dict (`mdhtml.export.tmpl_node`: `body`,
922
- `syntax`, `form`, `kind`, `name`, `inverted`) and returning a str for a literal text run,
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 convert module's helpers encode it)."""
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.2
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 to_mdhtml
34
- from mdhtml2docx import convert
33
+ from mdhtml import md2mdhtml
34
+ from mdhtml2docx import mdhtml2docx
35
35
 
36
- html = to_mdhtml(markdown_text)
37
- warnings = convert(html, 'out.docx')
36
+ html = md2mdhtml(markdown_text)
37
+ warnings = mdhtml2docx(html, 'out.docx')
38
38
  ```
39
39
 
40
- `convert` takes an MDHTML string, writes a docx, and returns a list of warning strings. It parses strings with `mdhtml.parse_mdhtml`, so normal HTML5 repair applies and XML well-formedness is irrelevant. It also accepts an existing mutable fast5ever DOM:
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 parse_mdhtml
43
+ from mdhtml import mdhtml2dom
44
44
 
45
- document = parse_mdhtml(html)
45
+ document = mdhtml2dom(html)
46
46
  document.children[0].attrs['custom-style'] = 'Contract Title'
47
- warnings = convert(document, 'out.docx')
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 convert time.
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
 
@@ -5,8 +5,8 @@ README.md
5
5
  pyproject.toml
6
6
  mdhtml2docx/__init__.py
7
7
  mdhtml2docx/asvocab.py
8
- mdhtml2docx/convert.py
9
8
  mdhtml2docx/hilite.py
9
+ mdhtml2docx/mdhtml2docx.py
10
10
  mdhtml2docx/styles.py
11
11
  mdhtml2docx/validate.py
12
12
  mdhtml2docx/wml.py
@@ -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, parse_mdhtml, to_mdhtml
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.convert import convert, mustache_fields
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
- convert('<p>Hello <em>world</em> with <strong>bold</strong> and <strong><em>both</em></strong>.</p>\n'
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
- convert(to_mdhtml('scores 3--5 -- nice... `a--b`', callbacks={'text': replacements(*DASHES)}), out)
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 = parse_mdhtml('<p>Before <div>middle</div> after.</p><span zoop:33="x">tail</span>'
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 = convert(dom, out)
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
- convert('<template><p>still hidden</p></template>', out)
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 = convert(
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 = convert(
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 = convert(
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 = convert(
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 = convert('<p>Euler: <span class="math inline">e^{i\\pi} + 1 = 0</span> inline.</p>\n'
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 = convert(to_mdhtml(sample_md(), callbacks={'text': replacements(*DASHES)}, implicit_figures=True), out, base=SAMPLE_MD.parent, number_headings='legal')
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 = convert('<pre><code class="language-python">def f(x):\n return "hi"\n</code></pre>\n'
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
- convert(mdhtml, out, reference=[ref_path(), 'dracula'])
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
- convert(mdhtml, out2, reference=[ref_path(), ref])
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
- convert(mdhtml, out3, reference=ref_path())
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 = convert(
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">&lt;w:r&gt;&lt;w:t&gt;RAW&lt;/w:t&gt;&lt;/w:r&gt;</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 = convert('<script type="application/vnd.mdhtml.raw" data-format="docx"><w:oops></script>', out)
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 = convert('<script type="application/vnd.mdhtml.raw" data-format="docx" data-encoding="base64">!!!</script>', out)
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 = convert(
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: convert('<p><a href="#nope" data-ref=""></a></p>', out), contains='#nope')
306
- tfail(lambda: convert('<p id="z-a"><a href="#z-a" data-ref=""></a></p>', out),
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: convert('<p><a href="#sec-pay" data-ref="page text"></a></p>', out), contains='conflicting data-ref')
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 = convert(
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
- convert('<h1 id="a">A</h1>', ref, number_headings='legal')
329
+ mdhtml2docx('<h1 id="a">A</h1>', ref, number_headings='legal')
330
330
  out = tmp_path/'t.docx'
331
- warns = convert('<h1 id="sec-a">A</h1>\n<ul>\n<li>one</li>\n</ul>\n'
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 = convert('<p custom-style="Fancy">Hi</p>\n<ul>\n<li>one</li>\n</ul>', out,
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 = convert(
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: convert('<div id="d-x"><p>hi</p></div><p><a href="#d-x" data-ref=""></a></p>', out),
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 = to_mdhtml('Pay {{sal}} to {{name}}.\n\n{{#opt}}\n\nGranted.\n\n{{/opt}}\n', templates=MUSTACHE)
392
- convert(src, out) # default: vars dropped, markers literal
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 = convert(src, out, tmpl=mustache_fields)
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
- convert(to_mdhtml('| A |\n|---|\n| b |\n{: custom-style="Borderless Table"}\n\n| C |\n|---|\n| d |\n'), out)
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['name']
414
+ def controls(node): return 'control', node['value']
415
415
  out = tmp_path/'t.docx'
416
- convert(to_mdhtml('Pay {{sal}} to {{name}}.\n\n{{#opt}}\n', templates=MUSTACHE), out, tmpl=controls)
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['name']
427
+ def bound(node): return 'bound', node['value']
428
428
  out = tmp_path/'t.docx'
429
- convert(to_mdhtml('Pay {{sal}} to {{sal}} and {{name}}.\n', templates=MUSTACHE), out, tmpl=bound)
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 = convert(to_mdhtml('::: {.details .tool-usage-details}\n## the label\n\nbody text\n:::\n'), out)
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, to_mdhtml
4
+ from mdhtml import DASHES, replacements, md2mdhtml
5
5
  from mdhtml.tools import SAMPLE_MD, sample_md
6
- from mdhtml2docx import convert
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 = convert(to_mdhtml(sample_md(), callbacks={'text': replacements(*DASHES)}, implicit_figures=True), docx, base=SAMPLE_MD.parent, number_headings='legal')
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
@@ -1,5 +0,0 @@
1
- __version__ = "0.1.2"
2
-
3
-
4
-
5
- from .convert import convert
File without changes
File without changes
File without changes
File without changes