213 lines
6.8 KiB
C#
213 lines
6.8 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.IO;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Text;
|
||
|
|
using DCIT.Core.Models;
|
||
|
|
using DCIT.Core.Utilities;
|
||
|
|
using DocumentFormat.OpenXml;
|
||
|
|
using DocumentFormat.OpenXml.Packaging;
|
||
|
|
using W = DocumentFormat.OpenXml.Wordprocessing;
|
||
|
|
|
||
|
|
namespace DCIT.Core.Services
|
||
|
|
{
|
||
|
|
public class WordTextExtractionService
|
||
|
|
{
|
||
|
|
private readonly IDocConversionService _docConversionService;
|
||
|
|
|
||
|
|
public WordTextExtractionService()
|
||
|
|
: this(new OfficeDocConversionService())
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
public WordTextExtractionService(IDocConversionService docConversionService)
|
||
|
|
{
|
||
|
|
_docConversionService = docConversionService;
|
||
|
|
}
|
||
|
|
|
||
|
|
public ExtractedDocumentText ExtractVisibleText(string filePath)
|
||
|
|
{
|
||
|
|
var workingPath = filePath;
|
||
|
|
string temporaryDocxPath = null;
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var extension = Path.GetExtension(filePath) ?? string.Empty;
|
||
|
|
if (extension.Equals(".doc", StringComparison.OrdinalIgnoreCase))
|
||
|
|
{
|
||
|
|
if (_docConversionService == null)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException("当前环境未配置 .doc 转换服务。");
|
||
|
|
}
|
||
|
|
|
||
|
|
temporaryDocxPath = Path.Combine(
|
||
|
|
Path.GetTempPath(),
|
||
|
|
"DCIT",
|
||
|
|
"extract",
|
||
|
|
Guid.NewGuid().ToString("N") + ".docx");
|
||
|
|
Directory.CreateDirectory(Path.GetDirectoryName(temporaryDocxPath) ?? Path.GetTempPath());
|
||
|
|
_docConversionService.Convert(filePath, temporaryDocxPath);
|
||
|
|
workingPath = temporaryDocxPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
using (var document = WordprocessingDocument.Open(workingPath, false))
|
||
|
|
{
|
||
|
|
var builder = new StringBuilder();
|
||
|
|
var rootElements = GetRelevantRootElements(document);
|
||
|
|
foreach (var root in rootElements)
|
||
|
|
{
|
||
|
|
AppendVisibleText(root, builder);
|
||
|
|
if (builder.Length > 0 && builder[builder.Length - 1] != '\n')
|
||
|
|
{
|
||
|
|
builder.AppendLine();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return new ExtractedDocumentText
|
||
|
|
{
|
||
|
|
FilePath = filePath,
|
||
|
|
VisibleText = builder.ToString(),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
TryDelete(temporaryDocxPath);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static IEnumerable<OpenXmlPartRootElement> GetRelevantRootElements(WordprocessingDocument document)
|
||
|
|
{
|
||
|
|
var result = new List<KeyValuePair<string, OpenXmlPartRootElement>>();
|
||
|
|
var partQueue = new Queue<OpenXmlPartContainer>();
|
||
|
|
partQueue.Enqueue(document.MainDocumentPart);
|
||
|
|
var visited = new HashSet<OpenXmlPart>();
|
||
|
|
|
||
|
|
while (partQueue.Count > 0)
|
||
|
|
{
|
||
|
|
var container = partQueue.Dequeue();
|
||
|
|
foreach (var pair in container.Parts)
|
||
|
|
{
|
||
|
|
var part = pair.OpenXmlPart;
|
||
|
|
if (part == null || !visited.Add(part))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (part.RootElement != null && IsRelevantPart(part))
|
||
|
|
{
|
||
|
|
result.Add(new KeyValuePair<string, OpenXmlPartRootElement>(GetStablePartKey(part), part.RootElement));
|
||
|
|
}
|
||
|
|
|
||
|
|
partQueue.Enqueue(part);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var ordered = result
|
||
|
|
.OrderBy(item => item.Key, StringComparer.OrdinalIgnoreCase)
|
||
|
|
.Select(item => item.Value)
|
||
|
|
.ToList();
|
||
|
|
|
||
|
|
if (document.MainDocumentPart != null && document.MainDocumentPart.Document != null)
|
||
|
|
{
|
||
|
|
ordered.Insert(0, document.MainDocumentPart.Document);
|
||
|
|
}
|
||
|
|
|
||
|
|
return ordered;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static bool IsRelevantPart(OpenXmlPart part)
|
||
|
|
{
|
||
|
|
return part is HeaderPart
|
||
|
|
|| part is FooterPart
|
||
|
|
|| part is WordprocessingCommentsPart
|
||
|
|
|| part is FootnotesPart
|
||
|
|
|| part is EndnotesPart
|
||
|
|
|| part.GetType().Name.IndexOf("ChartPart", StringComparison.OrdinalIgnoreCase) >= 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string GetStablePartKey(OpenXmlPart part)
|
||
|
|
{
|
||
|
|
return part == null || part.Uri == null
|
||
|
|
? string.Empty
|
||
|
|
: part.Uri.ToString();
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void AppendVisibleText(OpenXmlElement root, StringBuilder builder)
|
||
|
|
{
|
||
|
|
foreach (var element in root.Descendants())
|
||
|
|
{
|
||
|
|
var leaf = element as OpenXmlLeafTextElement;
|
||
|
|
if (leaf != null)
|
||
|
|
{
|
||
|
|
if (!IsVisibleLeaf(leaf))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
var text = leaf.Text;
|
||
|
|
if (string.IsNullOrWhiteSpace(text))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
builder.Append(text);
|
||
|
|
builder.Append(' ');
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!VmlTextMetadataUtility.IsVmlShapeElement(element))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (var binding in VmlTextMetadataUtility.CreateBindings(element))
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(binding.Text))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
builder.Append(binding.Text);
|
||
|
|
builder.Append(' ');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static bool IsVisibleLeaf(OpenXmlLeafTextElement leaf)
|
||
|
|
{
|
||
|
|
if (leaf is W.FieldCode || leaf is W.DeletedText)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (leaf.Ancestors<W.DeletedRun>().Any())
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
var run = leaf.Ancestors<W.Run>().FirstOrDefault();
|
||
|
|
if (run != null && run.RunProperties != null && run.RunProperties.Vanish != null)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void TryDelete(string path)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
|
||
|
|
{
|
||
|
|
File.Delete(path);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|