first commit
This commit is contained in:
212
DCIT.Core/Services/WordTextExtractionService.cs
Normal file
212
DCIT.Core/Services/WordTextExtractionService.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user