I'm on your side, when times get rough.

2021-03-20

[C#] Extract Text from XPS file.

Filed under: Programming — Peter_KIM @ 07:08
XPS 문서에 접근하기 위한 클래스(XpsDocument)가 .NET 프레임워크에서 제공됩니다.
텍스트 파일을 XPS Document Writer 가상 프린터에서 인쇄하여 OpenXPS 문서 파일로 출력했습니다.

많은 사람들이 “Stack Overflow”같은 사이트의 가르침대로, XPS 파일을 아래의 코드로 접근하려 시도했습니다.
그런데, GetFixedDocumentSequence 함수의 결과는 항상 null 값이었고, 더 이상 해당 파일에 접근할 수 없습니다.

XpsDocument oXpsDocument = new XpsDocument("..\\Path\\Test.oxps", System.IO.FileAccess.Read);
var vDocSeqReader = oXpsDocument.GetFixedDocumentSequence();
IXpsFixedDocumentReader oDocReader = vDocSeqReader.FixedDocuments[0];

정확한 이유를 모르겠으나, 어딘지 클래스의 함수에 오류가 있거나 XPS 문서에 문제가 있는 모양입니다.
현재 Open Document 형식은 XML 및 기타 리소스 파일들이 압축된 형태입니다. 오피스의 모든 문서들은 압축 도구를 이용하여 압축을 해제할 수 있습니다.
XPS 문서 역시 마찬가지입니다. 압축을 해제하면, 많은 XML 파일들이 보입니다.

파일의 Root 디렉터리에서 FixedDocumentSequence.fdseq 파일을 볼 수 있습니다.
파일의 이름은 앞의 코드에서 null 값을 반환하는 함수와 비슷합니다. 이 파일도 XML 구조로 되어 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<FixedDocumentSequence xmlns="http://schemas.openxps.org/oxps/v1.0">
    <DocumentReference Source="Documents/1/FixedDocument.fdoc" />
</FixedDocumentSequence>

Source 속성의 값이 가리키는 디렉터리에서 FixedDocument.fdoc 파일을 볼 수 있습니다. 이 파일은 모든 XPS 파일에 있습니다.
이 파일을 열어보니 XPS 문서 안에 있는 페이지의 위치 경로가 나타납니다. 결국에 페이지 파일(*.fpage)을 열었습니다.
문자열들은 Glyphs 노드의 UnicodeString 속성의 값으로 존재합니다.
압축된 XML 파일에서 XPATH 기법을 이용하여 노드를 분석하면 XPS 파일에서 텍스트를 추출할 수 있습니다.
압축된 파일을 다루기 위해서 ZipArchive, ZipArchiveEntry 클래스를 이용하고, XML 분석에서는 XmlDocument 클래스를 이용할 수 있습니다.

public string ExtractAllText(string sXpsFileName)
{
    string sAllText = string.Empty;
 
    using ZipArchive oXpsArchive = ZipFile.OpenRead(sXpsFileName);
 
    ZipArchiveEntry oFixedDoc = oXpsArchive.Entries.First(entry => entry.FullName.EndsWith("FixedDocument.fdoc"));
    if (oFixedDoc == null)
        return string.Empty;
 
    string sFixedDoc = ReadTextFromEntry(oFixedDoc);
    var vPages = GetPagesInfo(sFixedDoc);
 
    foreach (var vPage in vPages)
    {
        var vPageDoc = oXpsArchive.Entries.First(entry => entry.FullName.EndsWith(vPage));
        if (vPageDoc == null)
            continue;
 
        var sPageDoc = ReadTextFromEntry(vPageDoc);
        var vText = GetPageText(sPageDoc);
 
        sAllText = string.Concat(sAllTextvText);
 
    }
    return sAllText;
}
 
private string ReadTextFromEntry(ZipArchiveEntry oEntry)
{
    string sText = string.Empty;
 
    using var vStream = oEntry.Open();
    using (var vReader = new StreamReader(vStream))
    {
        sText = vReader.ReadToEnd();
        vReader.Close();
    }
    vStream.Close();
    return sText;
}
 
private string[] GetPagesInfo(string sXml)
{
    List<stringlstPage = new List<string>();
    XmlDocument xDoc = new XmlDocument();
    xDoc.LoadXml(sXml);
 
    XmlNodeList vNodes = xDoc.SelectNodes("//@Source");
    foreach (XmlNode xNode in vNodes)
    {
        lstPage.Add(xNode.Value);
    }
 
    return lstPage.ToArray();
}
 
private string GetPageText(string sXml)
{
    StringBuilder sbText = new StringBuilder();
 
    XmlDocument xDoc = new XmlDocument();
    xDoc.LoadXml(sXml);
 
    XmlNodeList vNodes = xDoc.SelectNodes("//*[name()='Glyphs']/@UnicodeString");
    foreach (XmlNode xNode in vNodes)
    {
        sbText.Append(xNode.Value);
    }
 
    return sbText.ToString();
}

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Create a free website or blog at WordPress.com.