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 GetRelevantRootElements(WordprocessingDocument document) { var result = new List>(); var partQueue = new Queue(); partQueue.Enqueue(document.MainDocumentPart); var visited = new HashSet(); 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(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().Any()) { return false; } var run = leaf.Ancestors().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 { } } } }