如何将大型 MATLAB 符号表达式自动插入为 Word 原生公式?
结论
可以自动完成。建议采用以下处理流程:
MATLAB symbolic expression
↓ mathml()
Presentation MathML
↓ MML2OMML.XSL
Office Math Markup Language(OMML)
↓ Word COM Range.InsertXML
Word 原生公式
不要把 mathml() 的返回值直接作为文本粘贴到 Word。Word 文档使用 OMML 保存原生公式,而非普通 MathML。需要先用 Microsoft Office 附带的 MML2OMML.XSL 将 MathML 转换为 OMML,再通过 Word COM 的 Range.InsertXML 插入文档。
整个过程不使用剪贴板,也不依赖浏览器或 HTML 页面,适合批量处理大型表达式。但它并非完全不受长度限制。单个公式过于复杂时,仍会受到 Word 公式排版性能、内存和页面宽度的影响。
为什么 mathml() 的结果会变成纯文本
MathML 和 OMML 是两种不同的格式:
- MathML 是通用的数学标记语言。
- OMML 是 Microsoft Word 在
.docx中使用的原生公式格式。
把 XML 字符串粘贴到 Word 时,Word 是否将其识别为公式,取决于剪贴板携带的数据格式和粘贴上下文。如果剪贴板中只有纯文本,Word 通常会直接插入 XML 文本。
从浏览器或 MathJax 复制公式有时能够成功,是因为浏览器可能同时向剪贴板写入 HTML、MathML 等格式,Word 会从中选择可以转换的内容。这种方式容易受到浏览器、剪贴板容量和 MathJax 输出方式的影响,不适合作为稳定的自动化接口。
使用 COM 的 Range.InsertXML,可以直接插入转换后的 OMML,完全绕过剪贴板。
准备工作
使用该方案需要满足以下条件:
- MATLAB 在 Windows 上运行。
- 计算机已安装桌面版 Microsoft Word。
- MATLAB 可以通过
actxserver使用 ActiveX/COM。 - 能够找到 Office 安装目录中的
MML2OMML.XSL。
该文件常见的安装位置如下:
C:\Program Files\Microsoft Office\root\Office16\MML2OMML.XSL
实际路径会随 Office 的安装方式和目录结构而变化,请以本机文件为准。可以使用 PowerShell 查找:
Get-ChildItem "C:\Program Files\Microsoft Office" -Filter MML2OMML.XSL -Recurse -ErrorAction SilentlyContinue
如果安装的是 32 位 Office,还可以检查:
Get-ChildItem "C:\Program Files (x86)\Microsoft Office" -Filter MML2OMML.XSL -Recurse -ErrorAction SilentlyContinue
完整 MATLAB 示例
以下代码接收一组符号表达式,调用 mathml() 生成 MathML,再用 Office 的 XSLT 转换为 OMML,最后创建 Word 文档并插入原生公式。
function symbolic_to_word(expressions, output_file, xsl_file)
%SYMBOLIC_TO_WORD Insert symbolic expressions as native Word equations.
%
% expressions : cell array containing symbolic expressions
% output_file : output .docx path
% xsl_file : full path to MML2OMML.XSL
arguments
expressions cell
output_file char
xsl_file char
end
if ~isfile(xsl_file)
error('MML2OMML.XSL was not found: %s', xsl_file);
end
output_file = char(java.io.File(output_file).getCanonicalPath());
word = [];
doc = [];
try
word = actxserver('Word.Application');
word.Visible = true;
doc = word.Documents.Add();
for k = 1:numel(expressions)
expr = expressions{k};
% MATLAB symbolic expression -> MathML
mml = mathml(expr);
% MathML -> Word OMML
omml = mathml_to_omml(mml, xsl_file);
% Insert immediately before Word's final paragraph mark
pos = doc.Content.End - 1;
rng = doc.Range(pos, pos);
rng.InsertXML(omml);
% Start a new paragraph for the next formula
if k < numel(expressions)
pos = doc.Content.End - 1;
rng = doc.Range(pos, pos);
rng.InsertAfter(char(13));
end
end
% 16 = wdFormatDocumentDefault (.docx)
doc.SaveAs2(output_file, 16);
doc.Close(0);
word.Quit();
delete(word);
fprintf('Saved Word document: %s\n', output_file);
catch ME
if ~isempty(doc)
try
doc.Close(0);
catch
end
end
if ~isempty(word)
try
word.Quit();
catch
end
try
delete(word);
catch
end
end
rethrow(ME);
end
end
function omml = mathml_to_omml(mml, xsl_file)
%MATHML_TO_OMML Convert MathML to Office Math Markup Language.
source = actxserver('Msxml2.DOMDocument.6.0');
style = actxserver('Msxml2.DOMDocument.6.0');
source.async = false;
source.validateOnParse = false;
source.resolveExternals = false;
style.async = false;
style.validateOnParse = false;
style.resolveExternals = false;
cleanupObj = onCleanup(@() release_dom_objects(source, style));
if ~source.loadXML(mml)
reason = source.parseError.reason;
error('Unable to parse MathML: %s', reason);
end
if ~style.load(xsl_file)
reason = style.parseError.reason;
error('Unable to load MML2OMML.XSL: %s', reason);
end
omml = source.transformNode(style);
% InsertXML needs the XML element, not an XML declaration.
omml = regexprep(omml, ...
'^\s*<\?xml[^>]*\?>\s*', '', 'once');
end
function release_dom_objects(source, style)
try
delete(source);
catch
end
try
delete(style);
catch
end
end
调用示例:
clear;
clc;
syms epsilon_i y_i t q K_0 K_1
syms phi_i(y_i) Gamma(q)
expr1 = epsilon_i == phi_i(y_i);
expr2 = diff(epsilon_i, t) + K_1 * epsilon_i;
expr3 = diff(epsilon_i, t) ...
+ K_1 * epsilon_i ...
+ K_0 * int(epsilon_i, t) ...
+ Gamma(q);
expressions = {
expr1
expr2
expr3
};
xsl_file = ...
'C:\Program Files\Microsoft Office\root\Office16\MML2OMML.XSL';
output_file = fullfile(pwd, 'symbolic_equations.docx');
symbolic_to_word(expressions, output_file, xsl_file);
插入后的内容会作为 Word 原生公式对象保存,可以继续在公式编辑器中修改,不会变成图片或普通 XML 文本。
如果只有 LaTeX 字符串
如果输入来自 latex(expr),可以尝试让 Word 直接解析线性公式:
word = actxserver('Word.Application');
word.Visible = true;
doc = word.Documents.Add();
latex_text = '\frac{x^2+1}{x-1}';
pos = doc.Content.End - 1;
rng = doc.Range(pos, pos);
rng.Text = latex_text;
equation = doc.OMaths.Add(rng);
equation.BuildUp();
doc.SaveAs2(fullfile(pwd, 'latex_equation.docx'), 16);
这种方式步骤更少,但可靠性不如转换为 OMML,主要有以下限制:
- Word 版本和公式输入设置会影响 LaTeX 解析。
- Word 只支持其公式编辑器能够接受的 LaTeX 子集。
equation、align等文档级环境不一定能直接解析。- MATLAB
latex()的输出不保证完全符合 Word 的 LaTeX 输入语法。 - 多行公式、矩阵和复杂定界符更容易遇到兼容问题。
简单表达式可以使用 OMaths.Add 和 BuildUp。批量处理复杂的 MATLAB 符号表达式时,建议使用 mathml()、MML2OMML.XSL 和 InsertXML。
处理超大型表达式时的建议
能够自动插入公式,不代表 Word 可以高效排版任意大小的单个公式。表达式过大时,Word 可能响应缓慢,公式也可能超出页面宽度,编辑时还会占用大量内存。
遇到这类问题,应从 MATLAB 的表达式树中拆分公式,不要直接截断 LaTeX 或 MathML 字符串。例如,可以按等号、加法项、矩阵行或推导步骤拆成多个完整表达式,再分别转换为 OMML。在字符串中间随意截断,可能破坏 XML、括号配对或运算符结构。
还可以采用以下方式:
- 将中间子表达式定义为单独变量。
- 把长等式拆成连续的推导步骤。
- 将大型矩阵按行或分块插入。
- 每处理一批公式便保存一次文档。
- 每个公式使用独立段落,减少公式对象之间的排版影响。
其他注意事项
actxserver('Word.Application')仅适用于 Windows 桌面版 Word,不能直接用于 macOS、Linux 或 Word Online。- 不要让 MSXML 执行外部来源提供的任意 XSLT 文件。此处应使用本机 Office 安装附带的
MML2OMML.XSL。 - 如果
loadXML报错,请先检查mathml()输出是否完整,以及其中是否存在无法解析的实体或损坏的 XML。 Range.InsertXML失败,通常是因为转换结果不是有效的 OMML、缺少命名空间,或者插入位置不适合放置公式。- 多行对齐公式需要在 MathML 或 OMML 中表示为表格结构。直接把
align环境文本交给 Word,无法保证得到相同的对齐效果。 - 在没有 Word 的环境中,也可以直接创建
.docx包,并向word/document.xml写入 OMML。但这种方法还需要正确维护命名空间、关系文件和包结构,开发成本通常高于 COM 方案。
备注:内容仅供参考。
