在这个数字化时代,文档的电子化和网络化发布变得越来越重要。为了简化和自动化公司文档的发布流程,开发了一款工具,旨在将Word文档转换为HTML格式,以便在网页上发布。
公司积累了大量的Word文档,需要发布到网站上。虽然Microsoft Word提供了导出为HTML的功能,但需要应用自定义的样式表,并且根据不同的文档类型应用不同的样式。基于这些需求,开发了一个原型工具,希望能够进一步改进。
这个原型工具虽然功能有限,但已经能够处理不同样式的文档,并取得了令人满意的结果。转换过程包括扫描Word文档,按阅读顺序提取每个单词或段落的样式属性,找到匹配的HTML标签,然后渲染成HTML文件。
阅读一个docx文档并不简单,它是一个XML格式,虽然可读,但规格庞大。经过几天的研究,理解了一些基本概念,并取得了一些成果。
首先,docx格式是一个压缩的zip包。可以使用自定义库打开它,也可以使用.NET Framework 3.0提供的“System.IO.Packaging”命名空间中的类,后者非常有用,因为它还实现了管理检索包“关系”的所有功能。
其次,想要分析的主要文件是包含文本的文件,即“\word\document.xml”。这个文件逐个元素描述了文档的内容。
第三,根据OOXML规范:“WordprocessingML文档的基础是其实际的文本内容。这些文本内容可以存储在许多上下文中(表格、文本框等),但WordprocessingML中最基本的文本内容形式是段落,使用p元素(§2.3.1.22)指定。在段落内,所有丰富的段落级格式都存储在pPr元素(§2.3.1.25;§2.3.1.26)中。[注意:一些段落属性的例子包括对齐、边框、连字符覆盖、缩进、行距、阴影、文本方向和寡/孤控制。]在段落内,文本被分组到一个或多个运行中,由r元素(§2.3.2.23)表示,它定义了一个具有一组共同属性的文本区域。”
段落可以有自己的样式,但包含的运行可以覆盖该样式,具有更具体的样式。因此,存在两个级别的样式。区分应用的样式(正常、标题、列表编号...)和文本修饰符(粗体、下划线、斜体...)是非常有用的。
程序在控制台输出所有在转换过程中未匹配的样式名称。
创建了一个结构,它将具有相同格式的所有文本保存在缓冲区中,并在格式更改时刷新缓冲区。StyleClass类执行此操作。它有两个公共属性StyleName和Modifiers,通过设置这些属性,可以检查格式何时更改。它覆盖了ToString()函数,返回一个包含其属性值的唯一字符串。每个段落都有自己的StyleClass,每个子元素也有。如果子元素指定了不同的样式或添加了文本修饰符,它将设置一个StyleClass,当样式更改时,它会使缓冲区刷新。
Private Class StyleClass
Public StyleName As String
Private p_modifiers As Hashtable
Public Sub New()
p_modifiers = New Hashtable
End Sub
Public Sub AddModifier(ByVal modifier As String)
If p_modifiers(modifier) Is Nothing Then
Me.p_modifiers.Add(modifier, modifier)
End If
End Sub
Public ReadOnly Property Modifiers() As Object
Get
Return p_modifiers.Values
End Get
End Property
Public Shadows Function ToString() As String
Dim tmp As String = ""
For Each m As String In p_modifiers.Values
tmp &= m
Next
Return StyleName & "|" & tmp
End Function
End Class
刷新缓冲区也意味着向文本添加HTML标签。创建了一个表格,其中包含Word样式及其匹配的“html开始标签”和“html结束标签”:
Style | Start tag | End tag
Normal | <p> | </p>
Heading1 | <h1> | </h1>
Code | <pre> | </pre>
...
以及一个用于文本修饰符的表格:
Modifier | Start tag | End tag
bold | <b> | </b>
Italic | <i> | </i>
Underline | <u> | </u>
...
从外部XML文件中加载这个匹配表到内存中,然后使用它根据规范渲染文本。
<?xml version="1.0" encoding="utf-8"?>
<conversion_map>
<styles>
<style name="Normale">
<start_ctag>[p]</start_ctag>
<end_ctag>[/p]</end_ctag>
</style>
<style name="Paragrafoelenco_l0">
<start_ctag>[li style='list-style-type: circle; margin: 5px 0 5px 15px;']</start_ctag>
<end_ctag>[/li]</end_ctag>
</style>
<style name="Code">
<start_ctag>[pre lang="VB.NET"]</start_ctag>
<end_ctag>[/pre]</end_ctag>
</style>
<style name="Grigliatabella">
<start_ctag>[table class='feature' cellspacing='0' cellpadding='0' style='width:100%;']</start_ctag>
<end_ctag>[/table]</end_ctag>
</style>
...
</styles>
<modifiers>
<modifer name="b">
<start_ctag>[b]</start_ctag>
<end_ctag>[/b]</end_ctag>
</modifer>
<modifer name="c">
<start_ctag>[text style='color: #{0};']</start_ctag>
<end_ctag>[/text]</end_ctag>
</modifer>
<modifer name="h">
<start_ctag>[text style='background-color: {0};']</start_ctag>
<end_ctag>[/text]</end_ctag>
</modifer>
...
</modifiers>
</conversion_map>
写入HTML文档的函数使用一个栈来添加开始标签和结束标签,以确保正确的顺序:
Dim tmp As String = ""
Dim cs As ConvertionClass = ht_Style(style.StyleName)
If cs Is Nothing Then
Console.WriteLine("Style not found: " & style.StyleName)
cs = New ConvertionClass() With {.StartTag = "", .EndTag = ""}
End If
Dim cm As ConvertionClass
Dim s As New Stack
tmp = cs.StartTag
For Each m In style.Modifiers
cm = ht_Mod(m)
If Not cm Is Nothing Then
s.Push(cm)
tmp &= cm.StartTag
End If
Next
tmp &= buffer
While s.Count > 0 AndAlso Not s.Peek Is Nothing
cm = s.Pop
tmp &= cm.EndTag
End While
tmp &= cs.EndTag
Return tmp