343 lines
11 KiB
C#
343 lines
11 KiB
C#
|
|
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; }
|
||
|
|
}
|
||
|
|
}
|