first commit
This commit is contained in:
103
DCIT.Core/Utilities/DisplayWidthUtility.cs
Normal file
103
DCIT.Core/Utilities/DisplayWidthUtility.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace DCIT.Core.Utilities
|
||||
{
|
||||
public static class DisplayWidthUtility
|
||||
{
|
||||
public static int Measure(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var width = 0;
|
||||
foreach (var character in value)
|
||||
{
|
||||
width += GetCharacterWidth(character);
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public static string TruncateToWidth(string value, int maxWidth, out bool truncated)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || maxWidth <= 0)
|
||||
{
|
||||
truncated = !string.IsNullOrEmpty(value);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var width = 0;
|
||||
truncated = false;
|
||||
|
||||
foreach (var character in value)
|
||||
{
|
||||
var charWidth = GetCharacterWidth(character);
|
||||
if (width + charWidth > maxWidth)
|
||||
{
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Append(character);
|
||||
width += charWidth;
|
||||
}
|
||||
|
||||
if (!truncated && builder.Length < value.Length)
|
||||
{
|
||||
truncated = true;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static string PadToWidth(string value, int targetWidth, out bool padded)
|
||||
{
|
||||
var builder = new StringBuilder(value ?? string.Empty);
|
||||
var currentWidth = Measure(builder.ToString());
|
||||
padded = currentWidth < targetWidth;
|
||||
|
||||
while (currentWidth + 2 <= targetWidth)
|
||||
{
|
||||
builder.Append('\u3000');
|
||||
currentWidth += 2;
|
||||
}
|
||||
|
||||
while (currentWidth < targetWidth)
|
||||
{
|
||||
builder.Append(' ');
|
||||
currentWidth += 1;
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static int GetCharacterWidth(char character)
|
||||
{
|
||||
if (character == '\u3000')
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (character >= 0x1100 &&
|
||||
(character <= 0x115F ||
|
||||
character == 0x2329 ||
|
||||
character == 0x232A ||
|
||||
(character >= 0x2E80 && character <= 0xA4CF && character != 0x303F) ||
|
||||
(character >= 0xAC00 && character <= 0xD7A3) ||
|
||||
(character >= 0xF900 && character <= 0xFAFF) ||
|
||||
(character >= 0xFE10 && character <= 0xFE19) ||
|
||||
(character >= 0xFE30 && character <= 0xFE6F) ||
|
||||
(character >= 0xFF00 && character <= 0xFF60) ||
|
||||
(character >= 0xFFE0 && character <= 0xFFE6)))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
270
DCIT.Core/Utilities/HashUtility.cs
Normal file
270
DCIT.Core/Utilities/HashUtility.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace DCIT.Core.Utilities
|
||||
{
|
||||
public static class HashUtility
|
||||
{
|
||||
private static readonly byte[] EncryptionKey = ComputeSha256Bytes("DCIT::DiffEncryptionKey::20260424");
|
||||
private static readonly byte[] TamperKey = ComputeSha256Bytes("DCIT::TamperProtectionKey::20260424");
|
||||
|
||||
public static string ComputeSha256Base64(byte[] bytes)
|
||||
{
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
return Convert.ToBase64String(sha.ComputeHash(bytes ?? new byte[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public static string ComputeSha256Base64(string value)
|
||||
{
|
||||
return ComputeSha256Base64(Encoding.UTF8.GetBytes(value ?? string.Empty));
|
||||
}
|
||||
|
||||
public static string ComputeFileSha256Base64(string path)
|
||||
{
|
||||
using (var stream = File.OpenRead(path))
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
return Convert.ToBase64String(sha.ComputeHash(stream));
|
||||
}
|
||||
}
|
||||
|
||||
public static string ComputeHmacBase64(byte[] bytes)
|
||||
{
|
||||
using (var hmac = new HMACSHA256(TamperKey))
|
||||
{
|
||||
return Convert.ToBase64String(hmac.ComputeHash(bytes ?? new byte[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public static string ComputeFileHmacBase64(string path)
|
||||
{
|
||||
using (var stream = File.OpenRead(path))
|
||||
using (var hmac = new HMACSHA256(TamperKey))
|
||||
{
|
||||
return Convert.ToBase64String(hmac.ComputeHash(stream));
|
||||
}
|
||||
}
|
||||
|
||||
public static EncryptedPayload Encrypt(byte[] plaintextBytes)
|
||||
{
|
||||
var iv = new byte[16];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(iv);
|
||||
}
|
||||
|
||||
byte[] cipherBytes;
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.Key = EncryptionKey;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
using (var encryptor = aes.CreateEncryptor())
|
||||
{
|
||||
cipherBytes = encryptor.TransformFinalBlock(plaintextBytes ?? new byte[0], 0, (plaintextBytes ?? new byte[0]).Length);
|
||||
}
|
||||
}
|
||||
|
||||
var tagInput = Combine(iv, cipherBytes);
|
||||
using (var hmac = new HMACSHA256(TamperKey))
|
||||
{
|
||||
return new EncryptedPayload
|
||||
{
|
||||
CiphertextBase64 = Convert.ToBase64String(cipherBytes),
|
||||
NonceBase64 = Convert.ToBase64String(iv),
|
||||
TagBase64 = Convert.ToBase64String(hmac.ComputeHash(tagInput)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static EncryptedPayloadHeader EncryptStream(Stream plaintextStream, Stream ciphertextStream)
|
||||
{
|
||||
if (plaintextStream == null)
|
||||
{
|
||||
throw new ArgumentNullException("plaintextStream");
|
||||
}
|
||||
|
||||
if (ciphertextStream == null)
|
||||
{
|
||||
throw new ArgumentNullException("ciphertextStream");
|
||||
}
|
||||
|
||||
if (!ciphertextStream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("密文流必须支持 Seek 以便计算防篡改标签。", "ciphertextStream");
|
||||
}
|
||||
|
||||
var iv = new byte[16];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(iv);
|
||||
}
|
||||
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.Key = EncryptionKey;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
using (var encryptor = aes.CreateEncryptor())
|
||||
using (var cryptoStream = new CryptoStream(ciphertextStream, encryptor, CryptoStreamMode.Write, leaveOpen: true))
|
||||
{
|
||||
plaintextStream.CopyTo(cryptoStream);
|
||||
cryptoStream.FlushFinalBlock();
|
||||
}
|
||||
}
|
||||
|
||||
ciphertextStream.Position = 0;
|
||||
using (var hmac = new HMACSHA256(TamperKey))
|
||||
{
|
||||
hmac.TransformBlock(iv, 0, iv.Length, iv, 0);
|
||||
var buffer = new byte[65536];
|
||||
int read;
|
||||
while ((read = ciphertextStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
hmac.TransformBlock(buffer, 0, read, buffer, 0);
|
||||
}
|
||||
|
||||
hmac.TransformFinalBlock(new byte[0], 0, 0);
|
||||
return new EncryptedPayloadHeader
|
||||
{
|
||||
NonceBase64 = Convert.ToBase64String(iv),
|
||||
TagBase64 = Convert.ToBase64String(hmac.Hash),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Decrypt(string ciphertextBase64, string nonceBase64, string tagBase64)
|
||||
{
|
||||
var cipherBytes = Convert.FromBase64String(ciphertextBase64 ?? string.Empty);
|
||||
var iv = Convert.FromBase64String(nonceBase64 ?? string.Empty);
|
||||
var providedTag = Convert.FromBase64String(tagBase64 ?? string.Empty);
|
||||
var expectedTag = Convert.FromBase64String(ComputeHmacBase64(Combine(iv, cipherBytes)));
|
||||
if (!FixedTimeEquals(providedTag, expectedTag))
|
||||
{
|
||||
throw new CryptographicException("差异载荷验证失败。");
|
||||
}
|
||||
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.Key = EncryptionKey;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
{
|
||||
return decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DecryptStream(Stream cipherStream, Stream plaintextStream, byte[] iv, byte[] expectedTag)
|
||||
{
|
||||
if (cipherStream == null)
|
||||
{
|
||||
throw new ArgumentNullException("cipherStream");
|
||||
}
|
||||
|
||||
if (plaintextStream == null)
|
||||
{
|
||||
throw new ArgumentNullException("plaintextStream");
|
||||
}
|
||||
|
||||
if (!cipherStream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("密文流必须支持 Seek 以便校验防篡改标签。", "cipherStream");
|
||||
}
|
||||
|
||||
using (var hmac = new HMACSHA256(TamperKey))
|
||||
{
|
||||
hmac.TransformBlock(iv ?? new byte[0], 0, (iv ?? new byte[0]).Length, iv ?? new byte[0], 0);
|
||||
cipherStream.Position = 0;
|
||||
var buffer = new byte[65536];
|
||||
int read;
|
||||
while ((read = cipherStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
hmac.TransformBlock(buffer, 0, read, buffer, 0);
|
||||
}
|
||||
|
||||
hmac.TransformFinalBlock(new byte[0], 0, 0);
|
||||
if (!FixedTimeEquals(hmac.Hash, expectedTag ?? new byte[0]))
|
||||
{
|
||||
throw new CryptographicException("差异载荷防篡改校验失败。");
|
||||
}
|
||||
}
|
||||
|
||||
cipherStream.Position = 0;
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.Key = EncryptionKey;
|
||||
aes.IV = iv;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
|
||||
using (var decryptor = aes.CreateDecryptor())
|
||||
using (var cryptoStream = new CryptoStream(cipherStream, decryptor, CryptoStreamMode.Read, leaveOpen: true))
|
||||
{
|
||||
cryptoStream.CopyTo(plaintextStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ComputeSha256Bytes(string value)
|
||||
{
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
return sha.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool FixedTimeEquals(byte[] left, byte[] right)
|
||||
{
|
||||
if (left == null || right == null || left.Length != right.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var diff = 0;
|
||||
for (var index = 0; index < left.Length; index++)
|
||||
{
|
||||
diff |= left[index] ^ right[index];
|
||||
}
|
||||
|
||||
return diff == 0;
|
||||
}
|
||||
|
||||
private static byte[] Combine(byte[] left, byte[] right)
|
||||
{
|
||||
left = left ?? new byte[0];
|
||||
right = right ?? new byte[0];
|
||||
var bytes = new byte[left.Length + right.Length];
|
||||
Buffer.BlockCopy(left, 0, bytes, 0, left.Length);
|
||||
Buffer.BlockCopy(right, 0, bytes, left.Length, right.Length);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public class EncryptedPayload
|
||||
{
|
||||
public string CiphertextBase64 { get; set; }
|
||||
|
||||
public string NonceBase64 { get; set; }
|
||||
|
||||
public string TagBase64 { get; set; }
|
||||
}
|
||||
|
||||
public class EncryptedPayloadHeader
|
||||
{
|
||||
public string NonceBase64 { get; set; }
|
||||
|
||||
public string TagBase64 { get; set; }
|
||||
}
|
||||
}
|
||||
60
DCIT.Core/Utilities/PathUtility.cs
Normal file
60
DCIT.Core/Utilities/PathUtility.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace DCIT.Core.Utilities
|
||||
{
|
||||
public static class PathUtility
|
||||
{
|
||||
public static string NormalizeDirectory(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var fullPath = Path.GetFullPath(path.Trim());
|
||||
if (!fullPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
{
|
||||
fullPath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
public static bool AreSameOrNestedDirectories(string left, string right)
|
||||
{
|
||||
var normalizedLeft = NormalizeDirectory(left);
|
||||
var normalizedRight = NormalizeDirectory(right);
|
||||
if (string.IsNullOrEmpty(normalizedLeft) || string.IsNullOrEmpty(normalizedRight))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return normalizedLeft.Equals(normalizedRight, StringComparison.OrdinalIgnoreCase)
|
||||
|| normalizedLeft.StartsWith(normalizedRight, StringComparison.OrdinalIgnoreCase)
|
||||
|| normalizedRight.StartsWith(normalizedLeft, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string GetRelativePath(string relativeTo, string path)
|
||||
{
|
||||
var root = new Uri(NormalizeDirectory(relativeTo), UriKind.Absolute);
|
||||
var target = new Uri(Path.GetFullPath(path), UriKind.Absolute);
|
||||
var relative = root.MakeRelativeUri(target).ToString().Replace('/', Path.DirectorySeparatorChar);
|
||||
return Uri.UnescapeDataString(relative);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ValidateDirectoryPair(string firstLabel, string firstPath, string secondLabel, string secondPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(firstPath) || string.IsNullOrWhiteSpace(secondPath))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (AreSameOrNestedDirectories(firstPath, secondPath))
|
||||
{
|
||||
yield return string.Format("{0} 与 {1} 不能相同,也不能互为父子目录。", firstLabel, secondLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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