mathml-to-omml 1.0.2__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.
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 amedama
4
+ Portions Copyright (c) 2026 Robert Danaraj (salorajan@gmail.com)
5
+ Portions Copyright (c) 2026 Daisy Project Contributors
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include examples *
4
+ recursive-include tests *
5
+ exclude scratch_test.py
6
+ exclude scratch_test_2.py
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: mathml_to_omml
3
+ Version: 1.0.2
4
+ Summary: Convert MathML to OMML (Office Math Markup Language) in pure Python
5
+ Home-page: https://github.com/salorajan/math_ml2omml
6
+ Author: amedama, w2w Contributors
7
+ Author-email: amedama <amedama@example.com>, w2w Contributors <contributors@w2w.org>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Text Processing :: Markup :: XML
19
+ Requires-Python: >=3.7
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: latex2mathml
23
+ Requires-Dist: lxml
24
+ Requires-Dist: python-docx
25
+ Dynamic: author
26
+ Dynamic: home-page
27
+ Dynamic: license-file
28
+ Dynamic: requires-python
29
+
30
+ # math_ml2omml
31
+
32
+ A pure Python library to convert MathML strings into OMML (Office Math Markup Language).
33
+
34
+ This is a modern, patched version of the original `mathml2omml` library, containing key bug fixes and enhancements developed for the `w2w` (Word-to-Word Math Compiler) project to ensure 100% parity with Microsoft Word's professional equation layouts.
35
+
36
+ ## Key Enhancements in this Patched Version
37
+ - **Accent and Group Character Rendering**: Fixed tag mismatches on `<m:groupChr>` inside `MUnder` and `MOver` classes. This ensures mathematical accents (like hats, tildes, dots) and underscript/overscript groupings compile and render correctly in Microsoft Word.
38
+ - **Improved Alignment and Layout Compatibility**: Programmatic mapping fixes to match standard Office OpenXML (OOXML) math specifications.
39
+
40
+ ## Installation
41
+
42
+ You can install it directly from this package directory:
43
+ ```bash
44
+ pip install .
45
+ ```
46
+
47
+ Or for development (editable mode):
48
+ ```bash
49
+ pip install -e .
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```python
55
+ import math_ml2omml
56
+
57
+ mathml = '<math><mi>x</mi><mo>+</mo><mi>y</mi></math>'
58
+ omml = math_ml2omml.convert(mathml)
59
+ print(omml)
60
+ ```
61
+
62
+ ### Custom Entities
63
+ If your MathML contains custom entities, pass them as a dictionary:
64
+ ```python
65
+ import html.entities
66
+ mathml = '<math><msubsup><mo>&int;</mo><mn>1</mn><mi>x</mi></msubsup></math>'
67
+ omml = math_ml2omml.convert(mathml, html.entities.name2codepoint)
68
+ ```
69
+
70
+ ## Running Tests
71
+
72
+ To verify the installation and run the suite of unit tests, execute the following command in the project root:
73
+ ```bash
74
+ python -m unittest discover -s tests
75
+ ```
76
+
77
+ ## Author
78
+ Patched and maintained by **Robert Danaraj** (salorajan@gmail.com).
79
+
80
+ ## Acknowledgements
81
+ Special thanks and deepest gratitude to **George Kerscher** ([IMLS Profile](https://www.imls.gov/about-us/national-museum-and-library-services-board/george-kerscher)), Chief Innovations Officer of the DAISY Consortium and President of the International Digital Publishing Forum. As a pioneering advocate for digital document accessibility (recognized as a White House Champion of Change), his invaluable discussion, guidance, and inspiration motivated the design, development, and deployment of this project.
82
+
83
+ We also thank the contributors of the [daisy-math-a11y](https://github.com/daisy/math-a11y) project for inspiring our work.
84
+
85
+ ## Keystroke Complexity Score (KCS)
86
+ This library includes a utility `calculate_kcs()` to compute the keyboard entry complexity cost of inputting mathematical equations into Microsoft Word's Equation Editor GUI. It is based on the **Keystroke-Level Model (KLM)** (Card, Moran, and Newell, 1980), adapted specifically for the interactive auto-build behaviors of Word's UnicodeMath input syntax:
87
+ - **Raw Keystrokes ($K$)**: Character count of the linear notation.
88
+ - **Cognitive Penalties ($C_i$)**: Additional weight for Shift keys (+1.0) and backslash escape sequences (+1.5).
89
+ - **Auto-Build Spacebars ($C_{spacebar}$)**: Weight for spacebar triggers required to compile structures like fractions, scripts, roots, and matrices (+2.0).
90
+ - **Fragility Penalty ($F$)**: Additional penalties (+10.0) for structures prone to user input errors without proper grouping.
91
+
92
+ ### Citations
93
+ - **Keystroke-Level Model (KLM)**: Card, S. K., Moran, T. P., and Newell, A. (1980). *The Keystroke-Level Model for User Performance Time with Interactive Systems*. Communications of the ACM, 23(7), 396–410.
94
+ - **UnicodeMath / Linear Math**: Murray Sargent III. (2010). *Unicode Sincerely, Math (Unicode Technical Note #28)*. Microsoft Corporation. [UTN #28 Specification](https://www.unicode.org/notes/tn28/UTN28-PlainTextMath-v3.pdf)
95
+
96
+
97
+ ## License
98
+ MIT License. See [LICENSE](LICENSE) for details. Portions copyright (c) 2026 Daisy Project Contributors.
99
+
100
+
@@ -0,0 +1,71 @@
1
+ # math_ml2omml
2
+
3
+ A pure Python library to convert MathML strings into OMML (Office Math Markup Language).
4
+
5
+ This is a modern, patched version of the original `mathml2omml` library, containing key bug fixes and enhancements developed for the `w2w` (Word-to-Word Math Compiler) project to ensure 100% parity with Microsoft Word's professional equation layouts.
6
+
7
+ ## Key Enhancements in this Patched Version
8
+ - **Accent and Group Character Rendering**: Fixed tag mismatches on `<m:groupChr>` inside `MUnder` and `MOver` classes. This ensures mathematical accents (like hats, tildes, dots) and underscript/overscript groupings compile and render correctly in Microsoft Word.
9
+ - **Improved Alignment and Layout Compatibility**: Programmatic mapping fixes to match standard Office OpenXML (OOXML) math specifications.
10
+
11
+ ## Installation
12
+
13
+ You can install it directly from this package directory:
14
+ ```bash
15
+ pip install .
16
+ ```
17
+
18
+ Or for development (editable mode):
19
+ ```bash
20
+ pip install -e .
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ import math_ml2omml
27
+
28
+ mathml = '<math><mi>x</mi><mo>+</mo><mi>y</mi></math>'
29
+ omml = math_ml2omml.convert(mathml)
30
+ print(omml)
31
+ ```
32
+
33
+ ### Custom Entities
34
+ If your MathML contains custom entities, pass them as a dictionary:
35
+ ```python
36
+ import html.entities
37
+ mathml = '<math><msubsup><mo>&int;</mo><mn>1</mn><mi>x</mi></msubsup></math>'
38
+ omml = math_ml2omml.convert(mathml, html.entities.name2codepoint)
39
+ ```
40
+
41
+ ## Running Tests
42
+
43
+ To verify the installation and run the suite of unit tests, execute the following command in the project root:
44
+ ```bash
45
+ python -m unittest discover -s tests
46
+ ```
47
+
48
+ ## Author
49
+ Patched and maintained by **Robert Danaraj** (salorajan@gmail.com).
50
+
51
+ ## Acknowledgements
52
+ Special thanks and deepest gratitude to **George Kerscher** ([IMLS Profile](https://www.imls.gov/about-us/national-museum-and-library-services-board/george-kerscher)), Chief Innovations Officer of the DAISY Consortium and President of the International Digital Publishing Forum. As a pioneering advocate for digital document accessibility (recognized as a White House Champion of Change), his invaluable discussion, guidance, and inspiration motivated the design, development, and deployment of this project.
53
+
54
+ We also thank the contributors of the [daisy-math-a11y](https://github.com/daisy/math-a11y) project for inspiring our work.
55
+
56
+ ## Keystroke Complexity Score (KCS)
57
+ This library includes a utility `calculate_kcs()` to compute the keyboard entry complexity cost of inputting mathematical equations into Microsoft Word's Equation Editor GUI. It is based on the **Keystroke-Level Model (KLM)** (Card, Moran, and Newell, 1980), adapted specifically for the interactive auto-build behaviors of Word's UnicodeMath input syntax:
58
+ - **Raw Keystrokes ($K$)**: Character count of the linear notation.
59
+ - **Cognitive Penalties ($C_i$)**: Additional weight for Shift keys (+1.0) and backslash escape sequences (+1.5).
60
+ - **Auto-Build Spacebars ($C_{spacebar}$)**: Weight for spacebar triggers required to compile structures like fractions, scripts, roots, and matrices (+2.0).
61
+ - **Fragility Penalty ($F$)**: Additional penalties (+10.0) for structures prone to user input errors without proper grouping.
62
+
63
+ ### Citations
64
+ - **Keystroke-Level Model (KLM)**: Card, S. K., Moran, T. P., and Newell, A. (1980). *The Keystroke-Level Model for User Performance Time with Interactive Systems*. Communications of the ACM, 23(7), 396–410.
65
+ - **UnicodeMath / Linear Math**: Murray Sargent III. (2010). *Unicode Sincerely, Math (Unicode Technical Note #28)*. Microsoft Corporation. [UTN #28 Specification](https://www.unicode.org/notes/tn28/UTN28-PlainTextMath-v3.pdf)
66
+
67
+
68
+ ## License
69
+ MIT License. See [LICENSE](LICENSE) for details. Portions copyright (c) 2026 Daisy Project Contributors.
70
+
71
+
@@ -0,0 +1,206 @@
1
+ import os
2
+ import sys
3
+ import argparse
4
+
5
+ # Gracefully check dependencies
6
+ missing_deps = []
7
+ try:
8
+ import lxml.etree
9
+ import lxml.html
10
+ except ImportError:
11
+ missing_deps.append("lxml")
12
+
13
+ try:
14
+ import docx
15
+ from docx.oxml import parse_xml
16
+ except ImportError:
17
+ missing_deps.append("python-docx")
18
+
19
+ if missing_deps:
20
+ print(f"Error: Missing required dependencies: {', '.join(missing_deps)}", file=sys.stderr)
21
+ print(f"Please install them using: pip install {' '.join(missing_deps)}", file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+ # Ensure we can import math_ml2omml from the local src/ directory
25
+ script_dir = os.path.dirname(os.path.abspath(__file__))
26
+ src_path = os.path.abspath(os.path.join(script_dir, "..", "src"))
27
+ sys.path.insert(0, src_path)
28
+
29
+ import math_ml2omml
30
+
31
+
32
+ def convert_html_to_docx(html_path: str, docx_path: str):
33
+ """
34
+ Parses an HTML file containing MathML equations, converts the equations
35
+ to OMML using math_ml2omml, and builds a WCAG-compliant structured Word document.
36
+ """
37
+ print(f"Reading HTML file: {html_path}")
38
+ if not os.path.exists(html_path):
39
+ raise FileNotFoundError(f"Source HTML file not found: {html_path}")
40
+
41
+ # 1. Parse the HTML document
42
+ with open(html_path, "r", encoding="utf-8") as f:
43
+ html_content = f.read()
44
+
45
+ parser = lxml.html.HTMLParser(encoding="utf-8")
46
+ doc_tree = lxml.html.fromstring(html_content, parser=parser)
47
+
48
+ # 2. Initialize a new Word Document
49
+ doc = docx.Document()
50
+
51
+ # 3. Add Document Title (Heading 1) - WCAG 2.2 Page structure
52
+ doc.add_heading("University Mathematics Equation Reference Database", level=1)
53
+ doc.add_paragraph(
54
+ "This document contains 200 mathematical equations spanning algebra, "
55
+ "calculus, differential equations, and discrete mathematics. All equations "
56
+ "are rendered as native Office Math Markup Language (OMML) blocks and accompanied "
57
+ "by alternative text descriptions to meet WCAG 2.2 accessibility guidelines."
58
+ )
59
+
60
+ # 4. Find all categories and process their problems
61
+ categories = doc_tree.xpath("//section[@class='category-section']")
62
+ print(f"Found {len(categories)} math categories. Processing equations...")
63
+
64
+ problem_count = 0
65
+ for category in categories:
66
+ # Category Heading (Heading 2)
67
+ cat_heading_elem = category.find("./h2")
68
+ cat_title = (
69
+ cat_heading_elem.text.strip()
70
+ if cat_heading_elem is not None
71
+ else "Untitled Category"
72
+ )
73
+ doc.add_heading(cat_title, level=2)
74
+
75
+ # Process each problem card
76
+ problems = category.xpath(".//article[@class='problem-card']")
77
+ for problem in problems:
78
+ # Problem Title (Heading 3)
79
+ prob_title_elem = problem.find("./h3")
80
+ prob_title = (
81
+ prob_title_elem.text.strip()
82
+ if prob_title_elem is not None
83
+ else "Untitled Problem"
84
+ )
85
+ doc.add_heading(prob_title, level=3)
86
+
87
+ # Get description
88
+ desc_elem = problem.xpath(".//p[@class='description']")
89
+ description_text = ""
90
+ if desc_elem:
91
+ full_desc = desc_elem[0].text_content().strip()
92
+ if "Accessibility Description:" in full_desc:
93
+ description_text = full_desc.split("Accessibility Description:")[1].strip()
94
+ else:
95
+ description_text = full_desc
96
+
97
+ # Get Unicode linear math
98
+ linear_elem = problem.xpath(".//p[@class='linear-math']")
99
+ linear_text = ""
100
+ if linear_elem:
101
+ full_linear = linear_elem[0].text_content().strip()
102
+ if "Unicode Linear Math Equivalent:" in full_linear:
103
+ linear_text = full_linear.split("Unicode Linear Math Equivalent:")[1].strip()
104
+ else:
105
+ linear_text = full_linear
106
+
107
+ # Get KCS score
108
+ kcs_elem = problem.xpath(".//p[@class='kcs-score']")
109
+ kcs_text = ""
110
+ if kcs_elem:
111
+ full_kcs = kcs_elem[0].text_content().strip()
112
+ if "Keyboard Complexity Score (KCS):" in full_kcs:
113
+ kcs_text = full_kcs.split("Keyboard Complexity Score (KCS):")[1].strip()
114
+ else:
115
+ kcs_text = full_kcs
116
+
117
+ # Locate the MathML element
118
+ math_elements = problem.xpath(".//math")
119
+ if math_elements:
120
+ math_elem = math_elements[0]
121
+ # Convert the lxml element to a MathML string
122
+ mathml_str = lxml.etree.tostring(math_elem, encoding="unicode")
123
+
124
+ try:
125
+ # Convert Presentation MathML to OMML XML string
126
+ omml_str = math_ml2omml.convert(mathml_str)
127
+
128
+ # Ensure OpenXML math namespaces are explicitly declared
129
+ if "xmlns:m=" not in omml_str:
130
+ omml_str = omml_str.replace(
131
+ "<m:oMath",
132
+ '<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"',
133
+ 1,
134
+ )
135
+
136
+ # Parse into a python-docx OxmlElement
137
+ omml_xml_element = parse_xml(omml_str)
138
+
139
+ # Create a paragraph for the equation
140
+ eq_para = doc.add_paragraph()
141
+ eq_para.paragraph_format.space_before = docx.shared.Pt(6)
142
+ eq_para.paragraph_format.space_after = docx.shared.Pt(6)
143
+ # Append the OMML math block directly to the paragraph's XML element
144
+ eq_para._p.append(omml_xml_element)
145
+
146
+ except Exception as e:
147
+ print(
148
+ f"Warning: Failed to convert math block for '{prob_title}': {e}"
149
+ )
150
+ # Fallback to plain text LaTeX representations if conversion fails
151
+ doc.add_paragraph(
152
+ f"[Conversion Error: MathML could not be compiled. {e}]"
153
+ )
154
+
155
+ # Add the descriptive text for screen readers/learning support (WCAG 2.2)
156
+ if description_text:
157
+ desc_para = doc.add_paragraph()
158
+ run = desc_para.add_run(f"Accessibility Description: {description_text}")
159
+ run.italic = True
160
+ run.font.color.rgb = docx.shared.RGBColor(100, 116, 139)
161
+
162
+ # Add Unicode linear math equivalent
163
+ if linear_text:
164
+ lin_para = doc.add_paragraph()
165
+ run = lin_para.add_run(f"Unicode Linear Math: {linear_text}")
166
+ run.font.color.rgb = docx.shared.RGBColor(30, 58, 138)
167
+ run.bold = True
168
+
169
+ # Add KCS score
170
+ if kcs_text:
171
+ kcs_para = doc.add_paragraph()
172
+ run = kcs_para.add_run(f"Keyboard Complexity Score (KCS): {kcs_text}")
173
+ run.font.color.rgb = docx.shared.RGBColor(185, 28, 28)
174
+ kcs_para.paragraph_format.space_after = docx.shared.Pt(18)
175
+
176
+ problem_count += 1
177
+
178
+ # 5. Save the compiled document
179
+ print(f"Saving Word document: {docx_path}")
180
+ doc.save(docx_path)
181
+ print(
182
+ f"Successfully converted {problem_count} equations and saved to {docx_path}"
183
+ )
184
+
185
+
186
+ if __name__ == "__main__":
187
+ parser = argparse.ArgumentParser(
188
+ description="Convert HTML containing MathML equations to a Word document."
189
+ )
190
+ parser.add_argument(
191
+ "--input", "-i",
192
+ default=os.path.join(script_dir, "html_data.html"),
193
+ help="Path to the input HTML file (default: html_data.html in script directory)"
194
+ )
195
+ parser.add_argument(
196
+ "--output", "-o",
197
+ default=os.path.join(script_dir, "omml_data.docx"),
198
+ help="Path to the output Word document (default: omml_data.docx in script directory)"
199
+ )
200
+ args = parser.parse_args()
201
+
202
+ try:
203
+ convert_html_to_docx(args.input, args.output)
204
+ except Exception as err:
205
+ print(f"Error executing conversion: {err}", file=sys.stderr)
206
+ sys.exit(1)
@@ -0,0 +1,212 @@
1
+ import os
2
+ import sys
3
+ import re
4
+ import argparse
5
+
6
+ # Gracefully check dependencies
7
+ missing_deps = []
8
+ try:
9
+ import latex2mathml.converter
10
+ except ImportError:
11
+ missing_deps.append("latex2mathml")
12
+
13
+ try:
14
+ import docx
15
+ from docx.oxml import parse_xml
16
+ except ImportError:
17
+ missing_deps.append("python-docx")
18
+
19
+ if missing_deps:
20
+ print(f"Error: Missing required dependencies: {', '.join(missing_deps)}", file=sys.stderr)
21
+ print(f"Please install them using: pip install {' '.join(missing_deps)}", file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+ # Ensure we can import math_ml2omml from the local src/ directory
25
+ script_dir = os.path.dirname(os.path.abspath(__file__))
26
+ src_path = os.path.abspath(os.path.join(script_dir, "..", "src"))
27
+ sys.path.insert(0, src_path)
28
+
29
+ try:
30
+ import math_ml2omml
31
+ except ImportError:
32
+ print("Error: Could not import 'math_ml2omml' from src directory.", file=sys.stderr)
33
+ sys.exit(1)
34
+
35
+
36
+ def main():
37
+ parser = argparse.ArgumentParser(
38
+ description="Convert Markdown containing LaTeX math equations to a Word document."
39
+ )
40
+ parser.add_argument(
41
+ "--input", "-i",
42
+ default=os.path.join(script_dir, "latex_math_data.md"),
43
+ help="Path to the input Markdown file (default: latex_math_data.md in script directory)"
44
+ )
45
+ parser.add_argument(
46
+ "--output", "-o",
47
+ default=os.path.join(script_dir, "omml_latex_data.docx"),
48
+ help="Path to the output Word document (default: omml_latex_data.docx in script directory)"
49
+ )
50
+ parser.add_argument(
51
+ "--reference-linear", "-r",
52
+ default=os.path.join(script_dir, "linear_math_data.md"),
53
+ help="Optional path to reference linear math file to ensure exact metadata matching"
54
+ )
55
+ args = parser.parse_args()
56
+
57
+ md_path = args.input
58
+ docx_path = args.output
59
+ ref_path = args.reference_linear
60
+
61
+ print(f"Reading Markdown file: {md_path}")
62
+ if not os.path.exists(md_path):
63
+ print(f"Error: Markdown file does not exist at {md_path}")
64
+ return
65
+
66
+ with open(md_path, "r", encoding="utf-8") as f:
67
+ lines = f.readlines()
68
+
69
+ # Load reference linear math equations if available to guarantee exact metadata and KCS matching
70
+ ref_equations = []
71
+ if ref_path and os.path.exists(ref_path):
72
+ print(f"Loading reference linear math from: {ref_path}")
73
+ with open(ref_path, "r", encoding="utf-8") as f:
74
+ ref_lines = f.readlines()
75
+ for line in ref_lines:
76
+ line = line.strip()
77
+ if line.startswith("**Equation:**"):
78
+ m = re.search(r'`(.*?)`', line)
79
+ if m:
80
+ ref_equations.append(m.group(1).strip())
81
+
82
+ categories = []
83
+ current_category = None
84
+ current_problem = None
85
+
86
+ for line in lines:
87
+ line = line.strip()
88
+ if line.startswith("## "):
89
+ category_name = line[3:].strip()
90
+ current_category = {"name": category_name, "problems": []}
91
+ categories.append(current_category)
92
+ elif line.startswith("### "):
93
+ title_part = line[4:].strip()
94
+ current_problem = {"title": title_part, "equation": "", "description": ""}
95
+ if current_category is not None:
96
+ current_category["problems"].append(current_problem)
97
+ elif line.startswith("**Equation:**"):
98
+ m = re.search(r'`(.*?)`', line)
99
+ if m and current_problem is not None:
100
+ current_problem["equation"] = m.group(1).strip()
101
+ elif line.startswith("**Description:**"):
102
+ desc_text = line[16:].strip()
103
+ if current_problem is not None:
104
+ current_problem["description"] = desc_text
105
+
106
+ print(f"Parsed {len(categories)} math categories. Initializing Word document compiler...")
107
+
108
+ # Initialize python-docx document
109
+ doc = docx.Document()
110
+
111
+ # Apply Standard Margins (1 inch)
112
+ sections = doc.sections
113
+ for section in sections:
114
+ section.top_margin = docx.shared.Inches(1)
115
+ section.bottom_margin = docx.shared.Inches(1)
116
+ section.left_margin = docx.shared.Inches(1)
117
+ section.right_margin = docx.shared.Inches(1)
118
+
119
+ # Document Header Title
120
+ doc.add_heading("University Mathematics Equation Reference Database (Compiled from LaTeX Math)", level=1)
121
+ doc.add_paragraph("This document serves as an empirical verification suite containing 200 university equations compiled directly from LaTeX math strings into native Word equation blocks (OMML) using the math_ml2omml package.")
122
+
123
+ problem_count = 0
124
+ for category in categories:
125
+ # Category Heading (Heading 2)
126
+ doc.add_heading(category["name"], level=2)
127
+
128
+ for problem in category["problems"]:
129
+ prob_title = problem["title"]
130
+ latex_str = problem["equation"]
131
+ description_text = problem["description"]
132
+
133
+ # Problem Title (Heading 3)
134
+ doc.add_heading(prob_title, level=3)
135
+
136
+ # Compile LaTeX directly to OMML using math_ml2omml (via MathML)
137
+ if latex_str:
138
+ try:
139
+ # Convert LaTeX to MathML
140
+ mathml_str = latex2mathml.converter.convert(latex_str)
141
+ # Convert MathML to OMML
142
+ omml_str = math_ml2omml.convert(mathml_str)
143
+
144
+ # Ensure OpenXML math namespaces are explicitly declared
145
+ if "xmlns:m=" not in omml_str:
146
+ omml_str = omml_str.replace(
147
+ "<m:oMath",
148
+ '<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"',
149
+ 1,
150
+ )
151
+
152
+ omml_xml_element = parse_xml(omml_str)
153
+
154
+ # Create a paragraph for the equation
155
+ eq_para = doc.add_paragraph()
156
+ eq_para.paragraph_format.space_before = docx.shared.Pt(6)
157
+ eq_para.paragraph_format.space_after = docx.shared.Pt(6)
158
+ eq_para._p.append(omml_xml_element)
159
+
160
+ except Exception as e:
161
+ print(f"Warning: Failed to convert LaTeX math for '{prob_title}': {e}")
162
+ doc.add_paragraph(f"[Conversion Error: LaTeX Math could not be compiled. {e}]")
163
+
164
+ # Determine the Unicode linear math representation
165
+ linear_str = ""
166
+ if problem_count < len(ref_equations):
167
+ # Use matching equation from reference linear math database
168
+ linear_str = ref_equations[problem_count]
169
+ else:
170
+ # Fallback to translating dynamically
171
+ if latex_str:
172
+ try:
173
+ linear_str = math_ml2omml.to_linear_math(latex_str)
174
+ except Exception as e:
175
+ print(f"Warning: Failed to translate to linear math dynamically for '{prob_title}': {e}")
176
+
177
+ # Calculate Keyboard Complexity Score (KCS)
178
+ kcs_score = 0.0
179
+ if linear_str:
180
+ try:
181
+ kcs_score = math_ml2omml.calculate_kcs(linear_str)
182
+ except Exception as e:
183
+ print(f"Warning: Failed to calculate KCS for '{prob_title}': {e}")
184
+
185
+ # Add Accessibility Description (WCAG 2.2)
186
+ if description_text:
187
+ desc_para = doc.add_paragraph()
188
+ run = desc_para.add_run(f"Accessibility Description: {description_text}")
189
+ run.italic = True
190
+ run.font.color.rgb = docx.shared.RGBColor(100, 116, 139)
191
+
192
+ # Add Unicode linear math equivalent (to match other documents)
193
+ if linear_str:
194
+ lin_para = doc.add_paragraph()
195
+ run = lin_para.add_run(f"Unicode Linear Math: {linear_str}")
196
+ run.font.color.rgb = docx.shared.RGBColor(30, 58, 138)
197
+ run.bold = True
198
+
199
+ # Add KCS score
200
+ kcs_para = doc.add_paragraph()
201
+ run = kcs_para.add_run(f"Keyboard Complexity Score (KCS): {kcs_score:.1f}")
202
+ run.font.color.rgb = docx.shared.RGBColor(185, 28, 28)
203
+ kcs_para.paragraph_format.space_after = docx.shared.Pt(18)
204
+
205
+ problem_count += 1
206
+
207
+ print(f"Saving compiled Word document: {docx_path}")
208
+ doc.save(docx_path)
209
+ print(f"Successfully converted and compiled {problem_count} equations to {docx_path}")
210
+
211
+ if __name__ == "__main__":
212
+ main()