Click here to Skip to main content
6,822,123 members and growing! (17,441 online)
Email Password   helpLost your password?
Enterprise Systems » Office Development » Microsoft Word     Intermediate

Using DocxToText to Extract Text from DOCX Files

By Eugene Pankov

This article explains how to extract text from DOCX files without Microsoft Office libraries.
C#2.0, Windows, .NET2.0, Visual-Studio, Dev
Posted:17 Sep 2007
Views:33,349
Bookmarked:36 times
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
12 votes for this article.
Popularity: 5.05 Rating: 4.68 out of 5

1

2
1 vote, 8.3%
3
2 votes, 16.7%
4
9 votes, 75.0%
5
DocxToText demo application

Introduction

At last, Microsoft has turned to XML-based format for storing document content. At the same time, it created a small problem for developers who need to index and search in Microsoft Word *.docx files. It's not a problem on a computer with Microsoft Office 2007 installed, but what is there to do if your application works on a server without Office and still needs to get text from Word files? Well, there are three options:

  • Install Microsoft Office 2007 and use its DLLs.
  • Use some third party libraries like "Office Open XML C# Library."
  • Write your own code.

In fact, there is another option: use the DocxToText class described below.

DocxToText Class

This class performs only one function: it extracts text from a given *.docx file. However, before we dig into the code, I'll remind you that a Microsoft Word *.docx file is an Open XML document combining texts, styles, graphics and so on into a single ZIP archive. Therefore we have to "unpack" the *.docx file to get to its guts. If you work with .NET Framework 3.0, you can use the Package class in the System.IO.Packaging namespace. However, working with .NET Framework 2.0, I used the open-source ZIP library SharpZipLib.

If you rename your *.docx file to *.zip and open it in your archiver, you will see a list of packed files like this:

Screenshot - screenshot2.png

First of all, we have to read the [Content_Types].xml file and find the location of the document.xml file. Usually, Microsoft hides it in the /word sub-directory, but it can be anywhere if the file was not created by Microsoft Word. Then we have to parse the document.xml file and extract text from it. A ReadNode() method does all the dirty work: it pulls out text strings, paragraphs, tabs and carriage returns, and concatenates it into final text.

Full text of the DocxToText class:

public class DocxToText
{
    private const string ContentTypeNamespace =
        @"http://schemas.openxmlformats.org/package/2006/content-types";

    private const string WordprocessingMlNamespace =
        @"http://schemas.openxmlformats.org/wordprocessingml/2006/main";

    private const string DocumentXmlXPath =
        "/t:Types/t:Override[@ContentType="" +
        "application/vnd.openxmlformats-officedocument." +
        "wordprocessingml.document.main+xml\"]";

    private const string BodyXPath = "/w:document/w:body";

    private string docxFile = "";
    private string docxFileLocation = "";

    public DocxToText(string fileName)
    {
        docxFile = fileName;
    }

    #region ExtractText()
    /// 

    /// Extracts text from the Docx file.

    /// 

    /// Extracted text.

    public string ExtractText()
    {
        if (string.IsNullOrEmpty(docxFile))
            throw new Exception("Input file not specified.");

        // Usually it is "/word/document.xml"


        docxFileLocation = FindDocumentXmlLocation();

        if (string.IsNullOrEmpty(docxFileLocation))
            throw new Exception("It is not a valid Docx file.");

        return ReadDocumentXml();
    }
    #endregion

    #region FindDocumentXmlLocation()
    /// 

    /// Gets location of the "document.xml" zip entry.

    /// 

    /// Location of the "document.xml".

    private string FindDocumentXmlLocation()
    {
        ZipFile zip = new ZipFile(docxFile);
        foreach (ZipEntry entry in zip)
        {
            // Find "[Content_Types].xml" zip entry


            if (string.Compare(entry.Name, "[Content_Types].xml", true) == 0)
            {
                Stream contentTypes = zip.GetInputStream(entry);

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.Load(contentTypes);
                contentTypes.Close();

                //Create an XmlNamespaceManager for resolving namespaces


                XmlNamespaceManager nsmgr = 
                    new XmlNamespaceManager(xmlDoc.NameTable);
                nsmgr.AddNamespace("t", ContentTypeNamespace);

                // Find location of "document.xml"


                XmlNode node = xmlDoc.DocumentElement.SelectSingleNode(
                    DocumentXmlXPath, nsmgr);

                if (node != null)
                {
                    string location = 
                        ((XmlElement) node).GetAttribute("PartName");
                    return location.TrimStart(new char[] {'/'});
                }
                break;
            }
        }
        zip.Close();
        return null;
    }
    #endregion

    #region ReadDocumentXml()
    /// 

    /// Reads "document.xml" zip entry.

    /// 

    /// Text containing in the document.

    private string ReadDocumentXml()
    {
        StringBuilder sb = new StringBuilder();

        ZipFile zip = new ZipFile(docxFile);
        foreach (ZipEntry entry in zip)
        {
            if (string.Compare(entry.Name, docxFileLocation, true) == 0)
            {
                Stream documentXml = zip.GetInputStream(entry);

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.Load(documentXml);
                documentXml.Close();

                XmlNamespaceManager nsmgr = 
                    new XmlNamespaceManager(xmlDoc.NameTable);
                nsmgr.AddNamespace("w", WordprocessingMlNamespace);

                XmlNode node = 
                    xmlDoc.DocumentElement.SelectSingleNode(BodyXPath,nsmgr);

                if (node == null)
                    return string.Empty;

                sb.Append(ReadNode(node));

                break;
            }
        }
        zip.Close();
        return sb.ToString();
    }
    #endregion

    #region ReadNode()
    /// 

    /// Reads content of the node and its nested childs.

    /// 

    /// XmlNode.

    /// Text containing in the node.

    private string ReadNode(XmlNode node)
    {
        if (node == null || node.NodeType != XmlNodeType.Element)
            return string.Empty;

        StringBuilder sb = new StringBuilder();
        foreach (XmlNode child in node.ChildNodes)
        {
            if (child.NodeType != XmlNodeType.Element) continue;

            switch (child.LocalName)
            {
                case "t":                           // Text

                    sb.Append(child.InnerText.TrimEnd());

                    string space = 
                        ((XmlElement)child).GetAttribute("xml:space");
                    if (!string.IsNullOrEmpty(space) && 
                        space == "preserve")
                        sb.Append(' ');

                    break;

                case "cr":                          // Carriage return

                case "br":                          // Page break

                    sb.Append(Environment.NewLine);
                    break;

                case "tab":                         // Tab

                    sb.Append("\t");
                    break;

                case "p":                           // Paragraph

                    sb.Append(ReadNode(child));
                    sb.Append(Environment.NewLine);
                    sb.Append(Environment.NewLine);
                    break;

                default:
                    sb.Append(ReadNode(child));
                    break;
            }
        }
        return sb.ToString();
    }
    #endregion
}

To extract text from a *.docx file using the DocxToText class, you need a few lines of code:

DocxToText dtt = new DocxToText(docxFileName);
string text = dtt.ExtractText();

Conclusion

The class is a bit primitive, but it performs its main function: to just extract text. It was quite enough to implement indexing and full-text search in *.docx files in my document storage and management system Heliocode Doc@Hand. The class does not extract page headers and footers; it does not process numbering and custom XML; similarly, it knows nothing about the data binding used in documents. If you improve the class, I'll be glad to hear about it.

History

September 17, 2007 - Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Eugene Pankov


Member
Eugene lives in Riga, Latvia. He started his programmer's career in 1983. Developed software for radio equipment CAD systems. Created computer graphics for TV. Worked in publishing houses. Developed Internet credit card processing systems for banks. Developed his own document management system. Now he is System Analyst in Accenture.
Occupation: Software Developer
Company: Accenture
Location: Latvia Latvia

Other popular Office Development articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 14 of 14 (Total in Forum: 14) (Refresh)FirstPrevNext
Generalyou saved us a lot of work Pinmemberdmihailescu11:23 1 Dec '09  
Questionhow about word(2000-2003) document Pinmembersatyamdelhi3:21 7 Aug '09  
AnswerRe: how about word(2000-2003) document PinmemberEugene Pankov7:14 7 Aug '09  
GeneralRe: how about word(2000-2003) document Pinmembersatyamdelhi7:26 7 Aug '09  
GeneralRe: how about word(2000-2003) document PinmemberEugene Pankov7:58 8 Aug '09  
Generalhow about parsing xlsx etc? PinmemberMartin Welker6:29 22 Jul '09  
GeneralThank You PinmemberS1n200912:03 26 Mar '09  
GeneralLiels paldies! Pinmembera kachanoff20:54 28 Dec '08  
Generalhow about images? PinmemberUnruled Boy21:21 25 Sep '08  
GeneralThank you very much! Pinmembersoxos113:26 14 Jul '08  
GeneralThank you Pinmemberrippo2:11 15 Oct '07  
GeneralSpecial thanks.. PinmemberPietro_SVK11:31 30 Sep '07  
GeneralGreat! PinsitebuilderUwe Keim20:37 17 Sep '07  
GeneralRe: Great! PinmemberEugene Pankov0:57 18 Sep '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.

PermaLink | Privacy | Terms of Use
Last Updated: 17 Sep 2007
Editor: Genevieve Sovereign
Copyright 2007 by Eugene Pankov
Everything else Copyright © CodeProject, 1999-2010
Web17 | Advertise on the Code Project