我有几个 Word 文档,每个文档包含几百页科学数据,其中包括:
- 化学式(H2SO4 带有所有适当的下标和上标)
- 科学数字(使用上标格式化的指数)
- 大量数学方程式。使用 Word 中的数学方程式编辑器编写。
问题是,将这些数据存储在 Word 中对我们来说效率不高。因此,我们希望将所有这些信息存储在数据库 (MySQL) 中。我们希望将格式转换为 LaTex。
有没有办法使用 VBA 遍历 Word 文档中的所有下标、上标和方程式?
答案1
是的。我建议使用 Powershell,因为它可以很好地处理 Word 文件。我认为这将是最简单的方法。
有关 Powershell 与 Word 自动化的更多信息,请参见此处:http://www.simple-talk.com/dotnet/.net-tools/com-automation-of-office-applications-via-powershell/
我深入挖掘了一下,发现了这个 powershell 脚本:
param([string]$docpath,[string]$htmlpath = $docpath)
$srcfiles = Get-ChildItem $docPath -filter "*.doc"
$saveFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveFormat], "wdFormatFilteredHTML");
$word = new-object -comobject word.application
$word.Visible = $False
function saveas-filteredhtml
{
$opendoc = $word.documents.open($doc.FullName);
$opendoc.saveas([ref]"$htmlpath\$doc.fullname.html", [ref]$saveFormat);
$opendoc.close();
}
ForEach ($doc in $srcfiles)
{
Write-Host "Processing :" $doc.FullName
saveas-filteredhtml
$doc = $null
}
$word.quit();
将其保存为 .ps1 并使用以下命令启动它:
convertdoc-tohtml.ps1 -docpath "C:\Documents" -htmlpath "C:\Output"
它将把指定目录中的所有 .doc 文件保存为 html 文件。因此,我有一个 doc 文件,其中有带下标的 H2SO4,经过 powershell 转换后,输出如下:
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=windows-1252">
<meta name=Generator content="Microsoft Word 14 (filtered)">
<style>
<!--
/* Font Definitions */
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin-top:0in;
margin-right:0in;
margin-bottom:10.0pt;
margin-left:0in;
line-height:115%;
font-size:11.0pt;
font-family:"Calibri","sans-serif";}
.MsoChpDefault
{font-family:"Calibri","sans-serif";}
.MsoPapDefault
{margin-bottom:10.0pt;
line-height:115%;}
@page WordSection1
{size:8.5in 11.0in;
margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
{page:WordSection1;}
-->
</style>
</head>
<body lang=EN-US>
<div class=WordSection1>
<p class=MsoNormal><span lang=PL>H<sub>2</sub>SO<sub>4</sub></span></p>
</div>
</body>
</html>
如您所见,下标在 HTML 中有自己的标签,因此剩下要做的就是在 bash 或 c++ 中解析文件以从 body 剪切到 /body,将其更改为 LATEX,然后删除其余的 HTML 标签。
代码来自http://blogs.technet.com/b/bshukla/archive/2011/09/27/3347395.aspx
因此,我用 C++ 开发了一个解析器来查找 HTML 下标并将其替换为 LATEX 下标。
代码:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
vector < vector <string> > parse( vector < vector <string> > vec, string filename )
{
/*
PARSES SPECIFIED FILE. EACH WORD SEPARATED AND
PLACED IN VECTOR FIELD.
REQUIRED INCLUDES:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
EXPECTS: TWO DIMENTIONAL VECTOR
STRING WITH FILENAME
RETURNS: TWO DIMENTIONAL VECTOR
vec[lines][words]
*/
string vword;
ifstream vfile;
string tmp;
// FILENAME CONVERSION FROM STING
// TO CHAR TABLE
char cfilename[filename.length()+1];
if( filename.length() < 126 )
{
for(int i = 0; i < filename.length(); i++)
cfilename[i] = filename[i];
cfilename[filename.length()] = '\0';
}
else return vec;
// OPENING FILE
//
vfile.open( cfilename );
if (vfile.is_open())
{
while ( vfile.good() )
{
getline( vfile, vword );
vector < string > vline;
vline.clear();
for (int i = 0; i < vword.length(); i++)
{
tmp = "";
// PARSING CONTENT. OMITTING SPACES AND TABS
//
while (vword[i] != ' ' && vword[i] != ((char)9) && i < vword.length() )
tmp += vword[i++];
if( tmp.length() > 0 ) vline.push_back(tmp);
}
if (!vline.empty())
vec.push_back(vline);
}
vfile.close();
}
else cout << "Unable to open file " << filename << ".\n";
return vec;
}
int main()
{
vector < vector < string > > vec;
vec = parse( vec, "parse.html" );
bool body = false;
for (int i = 0; i < vec.size(); i++)
{
for (int j = 0; j < vec[i].size(); j++)
{
if ( vec[i][j] == "<body") body=true;
if ( vec[i][j] == "</body>" ) body=false;
if ( body == true )
{
for ( int k=0; k < vec[i][j].size(); k++ )
{
if (k+4 < vec[i][j].size() )
{
if ( vec[i][j][k] == '<' &&
vec[i][j][k+1] == 's' &&
vec[i][j][k+2] == 'u' &&
vec[i][j][k+3] == 'b' &&
vec[i][j][k+4] == '>' )
{
string tmp = "";
while (vec[i][j][k+5] != '<')
{
tmp+=vec[i][j][k+5];
k++;
}
tmp = "_{" + tmp + "}";
k=k+5+5;
cout << tmp << endl;;
}
else cout << vec[i][j][k];
}
else cout << vec[i][j][k];
}
cout << endl;
}
}
}
return 0;
}
对于 html 文件:
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=windows-1252">
<meta name=Generator content="Microsoft Word 14 (filtered)">
<style>
<!--
/* Font Definitions */
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin-top:0in;
margin-right:0in;
margin-bottom:10.0pt;
margin-left:0in;
line-height:115%;
font-size:11.0pt;
font-family:"Calibri","sans-serif";}
.MsoChpDefault
{font-family:"Calibri","sans-serif";}
.MsoPapDefault
{margin-bottom:10.0pt;
line-height:115%;}
@page WordSection1
{size:8.5in 11.0in;
margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
{page:WordSection1;}
-->
</style>
</head>
<body lang=EN-US>
<div class=WordSection1>
<p class=MsoNormal><span lang=PL>H<sub>2</sub>SO<sub>4</sub></span></p>
</div>
</body>
</html>
输出为:
<body
lang=EN-US>
<div
class=WordSection1>
<p
class=MsoNormal><span
lang=PL>H_{2}
SO_{4}
</span></p>
</div>
当然,这并不是最理想的,但可以将其作为概念证明。
答案2
我一直在寻找一种与 mnmnc 所采取的方法不同的方法。
我尝试将测试 Word 文档保存为 HTML,但没有成功。我以前发现 Office 生成的 HTML 充满了无用的东西,几乎不可能挑出想要的部分。我发现这里就是这种情况。我在处理方程式时也遇到过问题。Word 将方程式保存为图像。每个方程式都有两幅图像,一个扩展名为 WMZ,一个扩展名为 GIF。如果使用 Google Chrome 显示 html 文件,方程式看起来还行,但不是很好;使用可以处理透明图像的图像显示/编辑工具显示时,外观与 GIF 文件一致。如果使用 Internet Explorer 显示 HTML 文件,方程式看起来很完美。HTML 引用了 WMZ 文件,因此我假设 Internet Explorer 包含一个扩展名来显示 WMZ 文件,这些文件显然是 Windows Media Player 皮肤,尽管 WMP 声称它们已损坏。
附加信息
我应该在原始答案中包含此信息。
我创建了一个小型 Word 文档,并将其保存为 Html。下图中的三个面板显示了原始 Word 文档、Microsoft Internet Explorer 显示的 Html 文档和 Google Chrome 显示的 Html 文档。
如前所述,IE 和 Chrome 图像之间的差异是由于方程式被保存了两次,一次以 WMZ 格式,一次以 GIF 格式。Html 太大,无法在此处显示。
该宏创建的Html是:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head><body>
<p>Some ordinary text.</p>
<p>H<sub>2</sub>SO<sub>4</sub>.</p>
<p>Abc & def > ghi < jkl</p>
<p>x<sup>3</sup>+ x<sup>2</sup>+3x+4=0.</p><p></p>
<p><i>Equation</i> </p>
<p>Mno</p>
<p><i>Equation</i></p>
</body></html>
显示为:
自从免费MathType 软件开发工具包显然包括转换为 LaTex 的例程
代码比较基础,所以注释不多。如有不清楚的地方可以询问。注意:这是原始代码的改进版本。
Sub ConvertToHtml()
Dim FileNum As Long
Dim NumPendingCR As Long
Dim objChr As Object
Dim PathCrnt As String
Dim rng As Word.Range
Dim WithinPara As Boolean
Dim WithinSuper As Boolean
Dim WithinSub As Boolean
FileNum = FreeFile
PathCrnt = ActiveDocument.Path
Open PathCrnt & "\TestWord.html" For Output Access Write Lock Write As #FileNum
Print #FileNum, "<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Frameset//EN""" & _
" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"">" & _
vbCr & vbLf & "<html xmlns=""http://www.w3.org/1999/xhtml"" " & _
"xml:lang=""en"" lang=""en"">" & vbCr & vbLf & _
"<head><meta http-equiv=""Content-Type"" content=""text/html; " _
& "charset=utf-8"" />" & vbCr & vbLf & "</head><body>"
For Each rng In ActiveDocument.StoryRanges
NumPendingCR = 0
WithinPara = False
WithinSub = False
WithinSuper = False
Do While Not (rng Is Nothing)
For Each objChr In rng.Characters
If objChr.Font.Superscript Then
If Not WithinSuper Then
' Start of superscript
Print #FileNum, "<sup>";
WithinSuper = True
End If
ElseIf WithinSuper Then
' End of superscript
Print #FileNum, "</sup>";
WithinSuper = False
End If
If objChr.Font.Subscript Then
If Not WithinSub Then
' Start of subscript
Print #FileNum, "<sub>";
WithinSub = True
End If
ElseIf WithinSub Then
' End of subscript
Print #FileNum, "</sub>";
WithinSub = False
End If
Select Case objChr
Case vbCr
NumPendingCR = NumPendingCR + 1
Case "&"
Print #FileNum, CheckPara(NumPendingCR, WithinPara) & "&";
Case "<"
Print #FileNum, CheckPara(NumPendingCR, WithinPara) & "<";
Case ">"
Print #FileNum, CheckPara(NumPendingCR, WithinPara) & ">";
Case Chr(1)
Print #FileNum, CheckPara(NumPendingCR, WithinPara) & "<i>Equation</i>";
Case Else
Print #FileNum, CheckPara(NumPendingCR, WithinPara) & objChr;
End Select
Next
Set rng = rng.NextStoryRange
Loop
Next
If WithinPara Then
Print #FileNum, "</p>";
withpara = False
End If
Print #FileNum, vbCr & vbLf & "</body></html>"
Close FileNum
End Sub
Function CheckPara(ByRef NumPendingCR As Long, _
ByRef WithinPara As Boolean) As String
' Have a character to output. Check paragraph status, return
' necessary commands and adjust NumPendingCR and WithinPara.
Dim RtnValue As String
RtnValue = ""
If NumPendingCR = 0 Then
If Not WithinPara Then
CheckPara = "<p>"
WithinPara = True
Else
CheckPara = ""
End If
Exit Function
End If
If WithinPara And (NumPendingCR > 0) Then
' Terminate paragraph
RtnValue = "</p>"
NumPendingCR = NumPendingCR - 1
WithinPara = False
End If
Do While NumPendingCR > 1
' Replace each pair of CRs with an empty paragraph
RtnValue = RtnValue & "<p></p>"
NumPendingCR = NumPendingCR - 2
Loop
RtnValue = RtnValue & vbCr & vbLf & "<p>"
WithinPara = True
NumPendingCR = 0
CheckPara = RtnValue
End Function
答案3
您可以直接从任何 2007 及以上的办公文档中提取 xml。具体操作如下:
- 将文件从 .docx 重命名为 .zip
- 使用 7zip(或其他解压程序)解压文件
- 要查看文档的实际内容,请查看解压后的文件
word
夹下的子文件夹和document.xml
文件。其中应包含文档的所有内容。
我创建了一个示例文档,并在正文标签中发现了这一点(请注意,我很快将其放在一起,因此格式可能有点不对):
<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<w:body>
-<w:p w:rsidRDefault="000E0C3A" w:rsidR="008B5DAA">
-<w:r>
<w:t xml:space="preserve">This </w:t>
</w:r>
- <w:r w:rsidRPr="000E0C3A">
-<w:rPr>
<w:vertAlign w:val="superscript"/>
</w:rPr>
<w:t>is</w:t>
</w:r>
- <w:r>
<w:t xml:space="preserve"> a </w:t>
</w:r>
-<w:r w:rsidRPr="000E0C3A">
-<w:rPr>
<w:vertAlign w:val="subscript"/>
</w:rPr>
<w:t>test</w:t>
</w:r>
-<w:r>
<w:t>.</w:t>
</w:r>
</w:p>
</w:body>
看起来<w:t>
标签是用于文本的,是<w:rPr>
字体的定义,<w:p>
是一个新段落。
单词等价的形式如下:
答案4
最简单的方法就是在 VBA 中使用以下几行代码:
Sub testing()
With ActiveDocument.Content.Find
.ClearFormatting
.Format = True
.Font.Superscript = True
.Execute Forward:=True
End With
End Sub
这将查找所有上标文本。如果您想用它做点什么,只需将其插入到方法中即可。例如,要在上标中查找单词“super”,并将其转换为“super found”,请使用:
Sub testing()
With ActiveDocument.Content.Find
.ClearFormatting
.Format = True
.Font.Superscript = True
.Execute Forward:=True, Replace:=wdReplaceAll, _
FindText:="super", ReplaceWith:="super found"
End With
End Sub