【问题标题】:Read the content of a Word document via its XML通过其 XML 读取 Word 文档的内容
【发布时间】:2016-08-24 14:07:01
【问题描述】:

上下文

我正在尝试在 Excel 中构建一个 Word 文档浏览器来筛选大量文档(大约 1000 个)。

事实证明,打开一个 word 文档的过程相当缓慢(每个文档大约需要 4 秒,因此在这种情况下需要 2 小时来查看所有项目,这对于单个查询来说太慢了),即使是禁用所有可能减慢打开速度的东西,因此我打开:

  • 只读
  • 没有打开和修复模式(某些文档可能会出现这种情况)
  • 禁用文档的显示

我目前的尝试

这些文档很难浏览,因为有些关键字确实每次都出现但不在同一个上下文中(不是问题的核心,因为当文本加载到数组中时我可以处理这个问题)。因此,在我的情况下,不能使用经常使用的 Windows explorer 解决方案(如 link )。

目前,我设法拥有一个工作宏,通过打开它们来分析 word 文档的内容。

代码

这是代码示例。 请注意,我使用了Microsoft Word 14.0 Object Library 参考

' Analyzing all the word document within the same folder '
Sub extractFile()

Dim i As Long, j As Long
Dim sAnalyzedDoc As String, sLibName As String
Dim aOut()
Dim oWordApp As Word.Application
Dim oDoc As Word.Document

Set oWordApp = CreateObject("Word.Application")

sLibName = ThisWorkbook.Path & "\"
sAnalyzedDoc = Dir(sLibName)
sKeyword = "example of a word"

With Application
    .DisplayAlerts = False
    .ScreenUpdating = False
End With

ReDim aOut(2, 2)
aOut(1, 1) = "Document name"
aOut(2, 1) = "Text"


While (sAnalyzedDoc <> "")
    ' Analyzing documents only with the .doc and .docx extension '
    If Not InStr(sAnalyzedDoc, ".doc") = 0 Then
        ' Opening the document as mentionned above, in read only mode, without repair and invisible '
        Set oDoc = Word.Documents.Open(sLibName & "\" & sAnalyzedDoc, ReadOnly:=True, OpenAndRepair:=False, Visible:=False)
        With oDoc
            For i = 1 To .Sentences.Count
                ' Searching for the keyword within the document '
                If Not InStr(LCase(.Sentences.Item(i)), LCase(sKeyword)) = 0 Then
                    If Not IsEmpty(aOut(1, 2)) Then
                        ReDim Preserve aOut(2, UBound(aOut, 2) + 1)
                    End If
                    aOut(1, UBound(aOut, 2)) = sAnalyzedDoc
                    aOut(2, UBound(aOut, 2)) = .Sentences.Item(i)
                    GoTo closingDoc ' A dubious programming choice but that works for the moment '
                End If
            Next i
closingDoc:
            ' Intending to make the closing faster by not saving the document '
            .Close SaveChanges:=False
        End With
    End If
    'Moving on to the next document '
    sAnalyzedDoc = Dir
Wend

exitSub:
With Output
    .Range(.Cells(1, 1), .Cells(UBound(aOut, 1), UBound(aOut, 2))) = aOut
End With

With Application
    .DisplayAlerts = True
    .ScreenUpdating = True
End With

End Sub

我的问题

我的想法是通过文档中的 XML 内容直接访问其内容(在更新版本的Word,带有.zip 扩展名并使用nameOfDocument.zip\word\document.xml)。

这将比加载所有在文本搜索中没有用的word文档的图像、图表和表格要快得多。

因此,我想问一下 VBA 中是否有一种方法可以打开像 zip 文件这样的 word 文档并访问该 XML 文档,然后像 VBA 中的普通字符串一样处理它,因为我已经有了上面代码给出的文件的路径和名称。

【问题讨论】:

  • 您可以通过 Shell 对象 (rondebruin.nl/win/s7/win002.htm) 直接访问压缩文件,但是您将无法解析 XML (stackoverflow.com/questions/11305/how-to-parse-xml-using-vba) 并且 Word 有一个可怕的底层 xml 可以使用.祝你好运。
  • 看看VBA macro to search a folder for a keyword。通过使用所描述的FindFiles 函数(使用第二个版本),您将利用文档中所有单词的 Windows 索引。
  • 谢谢你们,我会看看链接并尝试做点什么。
  • 好的,到目前为止,我已经得出结论,我想做的事情(即在不更改扩展名的情况下编辑 .docx)无法在 VBA 中完成。我目前正在用 C# 编写一个 DLL,它可能会解决类似于 code found on the MSDN 的问题,我希望尽快发布一些关于它的内容。

标签: excel xml vba ms-word


【解决方案1】:

请注意,这不是上述问题的简单答案,只要您没有大量文档要浏览,我最初问题中的唯一 VBA 代码就可以完美地完成工作,否则选择另一个工具(有一个 Python Dynamic Link Library (DLL) 做得很好)。

好的,我会尽量让我的回答解释清楚。

首先,这个问题将我引向了 C# 和 XPath 中 XML 的无限旅程,我在某些时候选择不去追求。

它将分析文件的时间从大约 2 小时减少到 10 秒。

上下文

阅读 XML 文档以及内部单词 XML 文档的核心是 Microsoft 的 OpenXML 库。 请记住我上面所说的,我试图实现的方法不能仅在 VBA 中完成,因此必须以另一种方式完成。 这可能是因为 VBA 是为 Office 实现的,因此在访问 Office 文档的核心结构方面受到限制,但我没有与此限制相关的信息(欢迎提供任何信息)。

我将在这里给出的答案是为 VBA 编写一个 C# DLL。 为了在 C# 中编写 DLL 并在 VBA 中引用它,我将您重定向到以下链接,该链接将以更好的方式恢复此特定过程:Tutorial for creating DLL in C#

开始吧

首先,您需要在项目中引用 WindowsBase 库和 DocumentFormat.OpenXML 以使解决方案按照此 MSDN 文章 Manipulate Office Open XML Formats Documents 和那个 Open and add text to a word processing document (Open XML SDK) 中的说明工作 这些文章广泛地解释了 OpenXML 库是如何处理 word 文档的。

C# 代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.IO.Packaging;

namespace BrowserClass
{

    public class SpecificDirectory
    {

        public string[,] LookUpWord(string nameKeyword, string nameStopword, string nameDirectory)
        {
            string sKeyWord = nameKeyword;
            string sStopWord = nameStopword;
            string sDirectory = nameDirectory;

            sStopWord = sStopWord.ToLower();
            sKeyWord = sKeyWord.ToLower();

            string sDocPath = Path.GetDirectoryName(sDirectory);
            // Looking for all the documents with the .docx extension
            string[] sDocName = Directory.GetFiles(sDocPath, "*.docx", SearchOption.AllDirectories);
            string[] sDocumentList = new string[1];
            string[] sDocumentText = new string[1];

            // Cycling the documents retrieved in the folder
            for (int i = 0; i < sDocName.Count(); i++)
            {
                string docWord = sDocName[i];

                // Opening the documents as read only, no need to edit them
                Package officePackage = Package.Open(docWord, FileMode.Open, FileAccess.Read);

                const String officeDocRelType = @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";

                PackagePart corePart = null;
                Uri documentUri = null;

                // We are extracting the part with the document content within the files
                foreach (PackageRelationship relationship in officePackage.GetRelationshipsByType(officeDocRelType))
                {
                    documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relationship.TargetUri);
                    corePart = officePackage.GetPart(documentUri);
                    break;
                }

                // Here enter the proper code
                if (corePart != null)
                {
                    string cpPropertiesSchema = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
                    string dcPropertiesSchema = "http://purl.org/dc/elements/1.1/";
                    string dcTermsPropertiesSchema = "http://purl.org/dc/terms/";

                    // Construction of a namespace manager to handle the different parts of the xml files
                    NameTable nt = new NameTable();
                    XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);
                    nsmgr.AddNamespace("dc", dcPropertiesSchema);
                    nsmgr.AddNamespace("cp", cpPropertiesSchema);
                    nsmgr.AddNamespace("dcterms", dcTermsPropertiesSchema);

                    // Loading the xml document's text
                    XmlDocument doc = new XmlDocument(nt);
                    doc.Load(corePart.GetStream());

                    // I chose to directly load the inner text because I could not parse the way I wanted the document, but it works so far
                    string docInnerText = doc.DocumentElement.InnerText;
                    docInnerText = docInnerText.Replace("\\* MERGEFORMAT", ".");
                    docInnerText = docInnerText.Replace("DOCPROPERTY ", "");
                    docInnerText = docInnerText.Replace("Glossary.", "");

                    try
                    {
                        Int32 iPosKeyword = docInnerText.ToLower().IndexOf(sKeyWord);
                        Int32 iPosStopWord = docInnerText.ToLower().IndexOf(sStopWord);

                        if (iPosStopWord == -1)
                        {
                            iPosStopWord = docInnerText.Length;
                        }

                        if (iPosKeyword != -1 && iPosKeyword <= iPosStopWord)
                        {
                            // Redimensions the array if there was already a document loaded
                            if (sDocumentList[0] != null)
                            {
                                Array.Resize(ref sDocumentList, sDocumentList.Length + 1);
                                Array.Resize(ref sDocumentText, sDocumentText.Length + 1);
                            }
                            sDocumentList[sDocumentList.Length - 1] = docWord.Substring(sDocPath.Length, docWord.Length - sDocPath.Length);
                            // Taking the small context around the keyword
                            sDocumentText[sDocumentText.Length - 1] = ("(...) " + docInnerText.Substring(iPosKeyword, sKeyWord.Length + 60) + " (...)");
                        }

                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        Console.WriteLine("Error reading inner text.");
                    }
                }
                // Closing the package to enable opening a document right after
                officePackage.Close();
            }

            if (sDocumentList[0] != null)
            {
                // Preparing the array for output
                string[,] sFinalArray = new string[sDocumentList.Length, 2];

                for (int i = 0; i < sDocumentList.Length; i++)
                {
                    sFinalArray[i, 0] = sDocumentList[i].Replace("\\", "");
                    sFinalArray[i, 1] = sDocumentText[i];
                }
                return sFinalArray;
            }
            else 
            {
                // Preparing the array for output
                string[,] sFinalArray = new string[1, 1];
                sFinalArray[0, 0] = "NO MATCH";
                return sFinalArray;
            }
        }
    }

}

关联的 VBA 代码

Option Explicit

Const sLibname As String = "C:\pathToYourDocuments\"

Sub tester()

Dim aFiles As Variant
Dim LookUpDir As BrowserClass.SpecificDirectory
Set LookUpDir = New BrowserClass.SpecificDirectory

' The array will contain all the files which contain the "searchedPhrase" '
aFiles = LookUpDir.LookUpWord("searchedPhrase", "stopWord", sLibname)

' Add here any necessary processing if needed '

End Sub

因此,最终您将获得一个扫描 .docx 文档的工具,其扫描速度比 VBA 中经典的打开-读取-关闭方法要快得多,但代价是编写更多代码。

最重要的是,您可以为只想执行简单搜索的用户提供一个简单的解决方案,尤其是在存在大量 word 文档时。

注意

正如@Mikegrann 所指出的,在 VBA 中解析 Word .XML 文件可能是一场噩梦。 值得庆幸的是,OpenXML 有一个 XML 解析器 C# , xml parsing. get data between tags,它将在 C# 中为您完成工作,并获取引用文档文本的 &lt;w:t&gt;&lt;/w:t&gt; 标记。尽管到目前为止我找到了这些答案,但无法使它们起作用: Parsing a MS Word generated XML file in C# , Reading specific XML elements from XML file

所以我选择了我在上面的代码中提供的.InnerText 解决方案,以访问内部文本,代价是有一些格式化文本输入(如\\MERGEFORMAT)。

【讨论】:

  • 是的,我确实忘记添加了。现在查询对所有文件执行大约需要 10 秒,但仅限于解析 .doc 文件
  • 太棒了!考虑发布一个 C# 问题并让专家展示如何集成 .docx 文件。
  • 该死,我刚回来看到我提到了.doc,而实际上这个答案只适用于.docx 文件,因为.doc 文件有不同的数据存储方式。抱歉,
猜你喜欢
  • 2017-09-20
  • 2016-12-19
  • 1970-01-01
  • 2017-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 2016-01-06
相关资源
最近更新 更多