first commit
This commit is contained in:
342
DCIT.Core/Utilities/VmlTextMetadataUtility.cs
Normal file
342
DCIT.Core/Utilities/VmlTextMetadataUtility.cs
Normal file
@@ -0,0 +1,342 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using DocumentFormat.OpenXml;
|
||||
|
||||
namespace DCIT.Core.Utilities
|
||||
{
|
||||
internal static class VmlTextMetadataUtility
|
||||
{
|
||||
private static readonly Regex Base64WhitespaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
|
||||
|
||||
public static bool IsVmlShapeElement(OpenXmlElement element)
|
||||
{
|
||||
return element != null
|
||||
&& string.Equals(element.LocalName, "shape", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(element.NamespaceUri, "urn:schemas-microsoft-com:vml", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static IEnumerable<VmlTextBinding> CreateBindings(OpenXmlElement shape)
|
||||
{
|
||||
if (!IsVmlShapeElement(shape))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var binding in CreateEquationBindings(shape))
|
||||
{
|
||||
yield return binding;
|
||||
}
|
||||
|
||||
foreach (var binding in CreateChartBindings(shape))
|
||||
{
|
||||
yield return binding;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<VmlTextBinding> CreateEquationBindings(OpenXmlElement shape)
|
||||
{
|
||||
var attribute = FindAttribute(shape, "equationxml");
|
||||
if (!attribute.HasValue || string.IsNullOrWhiteSpace(attribute.Value.Value))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var actualAttribute = attribute.Value;
|
||||
|
||||
XDocument document;
|
||||
try
|
||||
{
|
||||
var decoded = WebUtility.HtmlDecode(actualAttribute.Value);
|
||||
if (string.IsNullOrWhiteSpace(decoded))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
document = XDocument.Parse(decoded, LoadOptions.PreserveWhitespace);
|
||||
}
|
||||
catch
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var textNodes = document
|
||||
.Descendants()
|
||||
.Where(item => string.Equals(item.Name.LocalName, "t", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
if (textNodes.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var textNode in textNodes)
|
||||
{
|
||||
var initialText = textNode.Value ?? string.Empty;
|
||||
if (initialText.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return new VmlTextBinding
|
||||
{
|
||||
Text = initialText,
|
||||
IsEquationText = true,
|
||||
Apply = updatedText =>
|
||||
{
|
||||
textNode.Value = updatedText ?? string.Empty;
|
||||
SetAttribute(shape, actualAttribute, SerializeXml(document));
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<VmlTextBinding> CreateChartBindings(OpenXmlElement shape)
|
||||
{
|
||||
var attribute = FindAttribute(shape, "gfxdata");
|
||||
if (!attribute.HasValue || string.IsNullOrWhiteSpace(attribute.Value.Value))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var actualAttribute = attribute.Value;
|
||||
|
||||
ChartPackageContext context;
|
||||
try
|
||||
{
|
||||
context = ChartPackageContext.TryCreate(shape, actualAttribute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
context = null;
|
||||
}
|
||||
|
||||
if (context == null || context.XmlEntries.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var entry in context.XmlEntries)
|
||||
{
|
||||
foreach (var textNode in entry.Document
|
||||
.Descendants()
|
||||
.Where(item => IsChartVisibleTextNode(item))
|
||||
.ToList())
|
||||
{
|
||||
var initialText = textNode.Value ?? string.Empty;
|
||||
if (initialText.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return new VmlTextBinding
|
||||
{
|
||||
Text = initialText,
|
||||
IsChartText = true,
|
||||
Apply = updatedText =>
|
||||
{
|
||||
textNode.Value = updatedText ?? string.Empty;
|
||||
context.Save();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsChartVisibleTextNode(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var localName = element.Name.LocalName;
|
||||
return string.Equals(localName, "t", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(localName, "v", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static OpenXmlAttribute? FindAttribute(OpenXmlElement element, string localName)
|
||||
{
|
||||
if (element == null || string.IsNullOrWhiteSpace(localName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var attribute in element.GetAttributes())
|
||||
{
|
||||
if (string.Equals(attribute.LocalName, localName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void SetAttribute(OpenXmlElement element, OpenXmlAttribute sourceAttribute, string value)
|
||||
{
|
||||
element.SetAttribute(new OpenXmlAttribute(
|
||||
sourceAttribute.Prefix,
|
||||
sourceAttribute.LocalName,
|
||||
sourceAttribute.NamespaceUri,
|
||||
value ?? string.Empty));
|
||||
}
|
||||
|
||||
private static string SerializeXml(XDocument document)
|
||||
{
|
||||
using (var writer = new Utf8StringWriter())
|
||||
using (var xmlWriter = XmlWriter.Create(writer, new XmlWriterSettings
|
||||
{
|
||||
OmitXmlDeclaration = document.Declaration == null,
|
||||
Encoding = new UTF8Encoding(false),
|
||||
Indent = false,
|
||||
NewLineHandling = NewLineHandling.None,
|
||||
}))
|
||||
{
|
||||
document.Save(xmlWriter);
|
||||
xmlWriter.Flush();
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ChartPackageContext
|
||||
{
|
||||
private readonly OpenXmlElement _shape;
|
||||
private readonly OpenXmlAttribute _attribute;
|
||||
private readonly List<EntryState> _allEntries;
|
||||
|
||||
private ChartPackageContext(OpenXmlElement shape, OpenXmlAttribute attribute, List<EntryState> allEntries, List<EntryState> xmlEntries)
|
||||
{
|
||||
_shape = shape;
|
||||
_attribute = attribute;
|
||||
_allEntries = allEntries;
|
||||
XmlEntries = xmlEntries;
|
||||
}
|
||||
|
||||
public List<EntryState> XmlEntries { get; private set; }
|
||||
|
||||
public static ChartPackageContext TryCreate(OpenXmlElement shape, OpenXmlAttribute attribute)
|
||||
{
|
||||
var decoded = WebUtility.HtmlDecode(attribute.Value ?? string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(decoded))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalizedBase64 = Base64WhitespaceRegex.Replace(decoded, string.Empty);
|
||||
if (normalizedBase64.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String(normalizedBase64);
|
||||
var allEntries = new List<EntryState>();
|
||||
var xmlEntries = new List<EntryState>();
|
||||
|
||||
using (var input = new MemoryStream(bytes))
|
||||
using (var zip = new ZipArchive(input, ZipArchiveMode.Read, false))
|
||||
{
|
||||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
var state = new EntryState
|
||||
{
|
||||
FullName = entry.FullName,
|
||||
LastWriteTime = entry.LastWriteTime,
|
||||
};
|
||||
|
||||
using (var entryStream = entry.Open())
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
entryStream.CopyTo(memory);
|
||||
state.RawBytes = memory.ToArray();
|
||||
}
|
||||
|
||||
if (entry.FullName.IndexOf("drs/charts/", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
&& entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
state.Document = XDocument.Parse(
|
||||
Encoding.UTF8.GetString(state.RawBytes),
|
||||
LoadOptions.PreserveWhitespace);
|
||||
}
|
||||
catch
|
||||
{
|
||||
state.Document = null;
|
||||
}
|
||||
|
||||
if (state.Document != null)
|
||||
{
|
||||
xmlEntries.Add(state);
|
||||
}
|
||||
}
|
||||
|
||||
allEntries.Add(state);
|
||||
}
|
||||
}
|
||||
|
||||
return new ChartPackageContext(shape, attribute, allEntries, xmlEntries);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
using (var zip = new ZipArchive(output, ZipArchiveMode.Create, true))
|
||||
{
|
||||
foreach (var entry in _allEntries)
|
||||
{
|
||||
var targetEntry = zip.CreateEntry(entry.FullName, CompressionLevel.Optimal);
|
||||
targetEntry.LastWriteTime = entry.LastWriteTime;
|
||||
using (var targetStream = targetEntry.Open())
|
||||
{
|
||||
var bytes = entry.Document == null
|
||||
? (entry.RawBytes ?? Array.Empty<byte>())
|
||||
: Encoding.UTF8.GetBytes(SerializeXml(entry.Document));
|
||||
targetStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetAttribute(_shape, _attribute, Convert.ToBase64String(output.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Utf8StringWriter : StringWriter
|
||||
{
|
||||
public override Encoding Encoding
|
||||
{
|
||||
get { return new UTF8Encoding(false); }
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntryState
|
||||
{
|
||||
public string FullName { get; set; }
|
||||
|
||||
public DateTimeOffset LastWriteTime { get; set; }
|
||||
|
||||
public byte[] RawBytes { get; set; }
|
||||
|
||||
public XDocument Document { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class VmlTextBinding
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
||||
public bool IsEquationText { get; set; }
|
||||
|
||||
public bool IsChartText { get; set; }
|
||||
|
||||
public Action<string> Apply { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user