excel - 通过 XML 读取 Word 文档的内容

标签 excel xml vba ms-word

上下文

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

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

  • 只读
  • 没有打开和修复模式(这可能发生在某些文档上)
  • 禁用文档的显示

到目前为止我的尝试

这些文档很难浏览,因为有些关键字每次都会出现,但不会出现在相同的上下文中(这不是问题的核心,因为我可以在将文本加载到数组中时处理它)。因此,经常使用的 Windows 资源管理器 解决方案(如 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)。

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

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

最佳答案

请注意,这不是上述问题的简单答案,只要您没有要浏览的文档负载,我最初问题中的唯一 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# 中为您完成工作并获取 <w:t></w:t>引用文档文本的标签。虽然到目前为止我找到了这些答案但无法使它们起作用: Parsing a MS Word generated XML file in C# , Reading specific XML elements from XML file

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

关于excel - 通过 XML 读取 Word 文档的内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39125601/

相关文章:

java - JAXB,如何在错误的情况下解码不同的对象

android - 在 Android manifest 中,如何要求两个硬件中的至少一个?

c# - 如何从 "for xml path"获取结果?

excel - 在VBA中递归打印下一个字典

excel - 在 Excel 中的字符串后插入空白行

python - 如何将工作表转换为字符串?或者我什至需要这样做?

excel - 在 VBA 中动态存储单元格引用作为变量,然后使用存储的变量选择(和删除)范围

excel - 检查当前幻灯片的数量

javascript - jQuery 可自定义条形图

python - 在 Python flask 中上传、读取、写入 excel 文件