28 [NanoXlsxPlugIn(PlugInUUID = PlugInUUID.SharedStringsReader, PlugInOrder = 1000)]
29 internal class FormattedSharedStringsReader : ISharedStringReader
34 internal static readonly
int AUXILIARY_DATA_ID = 854563987;
37 private bool capturePhoneticCharacters;
38 private readonly List<PhoneticInfo> phoneticsInfo;
39 private MemoryStream stream;
40 private Workbook workbook;
51 public List<string> SharedStrings {
get;
private set; }
56 public Dictionary<string, FormattedText> FormattedTexts {
get;
private set; }
61 public Workbook Workbook {
get => workbook;
set => workbook = value; }
65 public IOptions Options {
get;
set; }
69 public Action<MemoryStream, Workbook, string, IOptions, int?> InlinePluginHandler {
get;
set; }
76 public FormattedSharedStringsReader()
78 phoneticsInfo =
new List<PhoneticInfo>();
79 SharedStrings =
new List<string>();
80 FormattedTexts =
new Dictionary<string, FormattedText>();
92 public void Init(MemoryStream stream, Workbook workbook, IOptions readerOptions, Action<MemoryStream, Workbook, string, IOptions, int?> inlinePluginHandler)
95 this.workbook = workbook;
96 if (readerOptions is ITextOptions options)
98 this.capturePhoneticCharacters = options.EnforcePhoneticCharacterImport;
100 this.InlinePluginHandler = inlinePluginHandler;
107 public void Execute()
113 XmlDocument xr =
new XmlDocument
117 bool hasFormattedText =
false;
118 using (XmlReader reader = XmlReader.Create(stream,
new XmlReaderSettings() { XmlResolver =
null }))
121 StringBuilder sb =
new StringBuilder();
122 foreach (XmlNode node
in xr.DocumentElement.ChildNodes)
124 if (node.LocalName.Equals(
"si", StringComparison.OrdinalIgnoreCase))
127 phoneticsInfo.Clear();
129 FormattedText formattedText = ProcessSharedStringItem(node, ref sb);
131 if (capturePhoneticCharacters)
133 textValue = ProcessPhoneticCharacters(sb);
134 formattedText.OverridePlainText(textValue);
136 else if (formattedText !=
null &&
string.IsNullOrEmpty(formattedText.
PlainText) && sb.
ToString().Length > 0)
138 textValue = sb.ToString();
139 formattedText.OverridePlainText(sb.ToString());
143 textValue = sb.ToString();
145 if (formattedText !=
null)
147 string key = PlugInUUID.SharedStringsReader + textValue;
148 SharedStrings.Add(key);
149 FormattedTexts[key] = formattedText;
150 hasFormattedText =
true;
154 SharedStrings.Add(textValue);
158 InlinePluginHandler?.Invoke(stream, Workbook, PlugInUUID.SharedStringsInlineReader, Options,
null);
160 if (hasFormattedText)
162 Workbook.AuxiliaryData.SetData(PlugInUUID.SharedStringsReader, AUXILIARY_DATA_ID, FormattedTexts);
168 throw new IOException(
"The XML entry could not be read from the " + nameof(stream) +
". Please see the inner exception:", ex);
178 private FormattedText ProcessSharedStringItem(XmlNode siNode, ref StringBuilder sb)
180 bool hasRuns =
false;
181 bool hasPhoneticRuns =
false;
182 XmlNode phoneticPropertiesNode =
null;
185 foreach (XmlNode childNode
in siNode.ChildNodes)
187 if (childNode.LocalName.Equals(
"r", StringComparison.OrdinalIgnoreCase))
191 else if (childNode.LocalName.Equals(
"rPh", StringComparison.OrdinalIgnoreCase))
193 hasPhoneticRuns =
true;
195 else if (childNode.LocalName.Equals(
"phoneticPr", StringComparison.OrdinalIgnoreCase))
197 phoneticPropertiesNode = childNode;
201 if (!hasRuns && !hasPhoneticRuns && phoneticPropertiesNode ==
null)
204 GetTextToken(siNode, ref sb);
214 foreach (XmlNode childNode
in siNode.ChildNodes)
216 if (childNode.LocalName.Equals(
"r", StringComparison.OrdinalIgnoreCase))
218 ProcessTextRun(childNode, formattedText, ref sb);
225 GetTextToken(siNode, ref sb);
231 foreach (XmlNode childNode
in siNode.ChildNodes)
233 if (childNode.LocalName.Equals(
"rPh", StringComparison.OrdinalIgnoreCase))
235 ProcessPhoneticRun(childNode, formattedText);
241 if (phoneticPropertiesNode !=
null)
243 ProcessPhoneticProperties(phoneticPropertiesNode, formattedText);
246 return formattedText;
255 private void ProcessTextRun(XmlNode runNode,
FormattedText formattedText, ref StringBuilder sb)
257 Font fontStyle =
null;
260 foreach (XmlNode childNode
in runNode.ChildNodes)
262 if (childNode.LocalName.Equals(
"rPr", StringComparison.OrdinalIgnoreCase))
264 fontStyle = ParseRunProperties(childNode);
266 else if (childNode.LocalName.Equals(
"t", StringComparison.OrdinalIgnoreCase))
268 text = childNode.InnerText;
273 if (!
string.IsNullOrEmpty(text))
275 formattedText.
AddRun(text, fontStyle);
284 private Font ParseRunProperties(XmlNode rPrNode)
286 Font font =
new Font();
288 foreach (XmlNode childNode
in rPrNode.ChildNodes)
290 string nodeName = childNode.LocalName;
292 if (nodeName.Equals(
"rFont", StringComparison.OrdinalIgnoreCase))
294 font.Name = GetAttributeValue(childNode,
"val");
296 else if (nodeName.Equals(
"charset", StringComparison.OrdinalIgnoreCase))
298 string charsetValue = GetAttributeValue(childNode,
"val");
299 if (!
string.IsNullOrEmpty(charsetValue))
301 font.Charset = (Font.CharsetValue)ParserUtils.ParseInt(charsetValue);
304 else if (nodeName.Equals(
"family", StringComparison.OrdinalIgnoreCase))
306 string familyValue = GetAttributeValue(childNode,
"val");
307 if (!
string.IsNullOrEmpty(familyValue))
309 font.Family = (Font.FontFamilyValue)ParserUtils.ParseInt(familyValue);
312 else if (nodeName.Equals(
"b", StringComparison.OrdinalIgnoreCase))
316 else if (nodeName.Equals(
"i", StringComparison.OrdinalIgnoreCase))
320 else if (nodeName.Equals(
"strike", StringComparison.OrdinalIgnoreCase))
324 else if (nodeName.Equals(
"outline", StringComparison.OrdinalIgnoreCase))
328 else if (nodeName.Equals(
"shadow", StringComparison.OrdinalIgnoreCase))
332 else if (nodeName.Equals(
"condense", StringComparison.OrdinalIgnoreCase))
334 font.Condense =
true;
336 else if (nodeName.Equals(
"extend", StringComparison.OrdinalIgnoreCase))
340 else if (nodeName.Equals(
"color", StringComparison.OrdinalIgnoreCase))
342 font.ColorValue = ParseColor(childNode);
344 else if (nodeName.Equals(
"sz", StringComparison.OrdinalIgnoreCase))
346 string sizeValue = GetAttributeValue(childNode,
"val");
347 if (!
string.IsNullOrEmpty(sizeValue))
349 font.Size = ParserUtils.ParseFloat(sizeValue);
352 else if (nodeName.Equals(
"u", StringComparison.OrdinalIgnoreCase))
354 string underlineValue = GetAttributeValue(childNode,
"val");
355 if (
string.IsNullOrEmpty(underlineValue))
357 font.Underline = Font.UnderlineValue.Single;
361 font.Underline = ParseUnderlineValue(underlineValue);
364 else if (nodeName.Equals(
"vertAlign", StringComparison.OrdinalIgnoreCase))
366 string vertAlignValue = GetAttributeValue(childNode,
"val");
367 if (!
string.IsNullOrEmpty(vertAlignValue))
369 font.VerticalAlign = ParseVerticalAlignValue(vertAlignValue);
372 else if (nodeName.Equals(
"scheme", StringComparison.OrdinalIgnoreCase))
374 string schemeValue = GetAttributeValue(childNode,
"val");
375 if (!
string.IsNullOrEmpty(schemeValue))
377 font.Scheme = ParseSchemeValue(schemeValue);
390 private Color ParseColor(XmlNode colorNode)
392 string autoValue = GetAttributeValue(colorNode,
"auto");
393 string indexedValue = GetAttributeValue(colorNode,
"indexed");
394 string rgbValue = GetAttributeValue(colorNode,
"rgb");
395 string themeValue = GetAttributeValue(colorNode,
"theme");
396 string systemValue = GetAttributeValue(colorNode,
"system");
397 string tintValue = GetAttributeValue(colorNode,
"tint");
401 if (!
string.IsNullOrEmpty(autoValue))
403 color = Color.CreateAuto();
405 else if (!
string.IsNullOrEmpty(indexedValue))
407 color = Color.CreateIndexed(ParserUtils.ParseInt(indexedValue));
409 else if (!
string.IsNullOrEmpty(rgbValue))
411 color = Color.CreateRgb(rgbValue);
413 else if (!
string.IsNullOrEmpty(themeValue))
415 color = Color.CreateTheme(ParserUtils.ParseInt(themeValue));
417 else if (!
string.IsNullOrEmpty(systemValue))
419 color = Color.CreateSystem(SystemColor.MapStringToValue(systemValue));
421 if (color !=
null && !
string.IsNullOrEmpty(tintValue))
423 color.Tint = ParserUtils.ParseFloat(tintValue);
434 private void ProcessPhoneticRun(XmlNode rPhNode,
FormattedText formattedText)
436 string startBase = GetAttributeValue(rPhNode,
"sb");
437 string endBase = GetAttributeValue(rPhNode,
"eb");
440 foreach (XmlNode childNode
in rPhNode.ChildNodes)
442 if (childNode.LocalName.Equals(
"t", StringComparison.OrdinalIgnoreCase))
444 text = childNode.InnerText;
448 if (!
string.IsNullOrEmpty(text) && !
string.IsNullOrEmpty(startBase) && !
string.IsNullOrEmpty(endBase))
450 uint sb = (uint)ParserUtils.ParseInt(startBase);
451 uint eb = (uint)ParserUtils.ParseInt(endBase);
455 if (capturePhoneticCharacters)
457 phoneticsInfo.Add(
new PhoneticInfo(text, startBase, endBase));
467 private void ProcessPhoneticProperties(XmlNode phoneticPrNode,
FormattedText formattedText)
469 string fontIdValue = GetAttributeValue(phoneticPrNode,
"fontId");
470 string typeValue = GetAttributeValue(phoneticPrNode,
"type");
471 string alignmentValue = GetAttributeValue(phoneticPrNode,
"alignment");
474 Font fontReference =
new Font();
475 if (!
string.IsNullOrEmpty(fontIdValue))
482 if (!
string.IsNullOrEmpty(typeValue))
484 type = ParsePhoneticType(typeValue);
488 if (!
string.IsNullOrEmpty(alignmentValue))
490 alignment = ParsePhoneticAlignment(alignmentValue);
501 private void GetTextToken(XmlNode node, ref StringBuilder sb)
503 if (node.LocalName.Equals(
"t", StringComparison.OrdinalIgnoreCase) && !
string.IsNullOrEmpty(node.InnerText))
505 sb.Append(node.InnerText);
507 if (node.HasChildNodes)
509 foreach (XmlNode childNode
in node.ChildNodes)
511 if (childNode.LocalName.Equals(
"rPh", StringComparison.OrdinalIgnoreCase))
515 GetTextToken(childNode, ref sb);
525 private string ProcessPhoneticCharacters(StringBuilder sb)
528 StringBuilder sb2 =
new StringBuilder();
529 int currentTextIndex = 0;
530 foreach (PhoneticInfo info
in phoneticsInfo)
532 sb2.Append(text.Substring(currentTextIndex, info.StartIndex + info.Length - currentTextIndex));
533 sb2.Append(
'(').Append(info.Value).Append(
')');
534 currentTextIndex = info.StartIndex + info.Length;
536 sb2.Append(text.Substring(currentTextIndex));
538 return sb2.ToString();
547 private string GetAttributeValue(XmlNode node,
string attributeName)
549 XmlNode attribute = node.Attributes?.GetNamedItem(attributeName);
550 return attribute?.InnerText;
558 private Font.UnderlineValue ParseUnderlineValue(
string value)
560 switch (value.ToLowerInvariant())
563 return Font.UnderlineValue.Double;
564 case "singleaccounting":
565 return Font.UnderlineValue.SingleAccounting;
566 case "doubleaccounting":
567 return Font.UnderlineValue.DoubleAccounting;
569 return Font.UnderlineValue.Single;
578 private Font.VerticalTextAlignValue ParseVerticalAlignValue(
string value)
580 switch (value.ToLowerInvariant())
583 return Font.VerticalTextAlignValue.Superscript;
585 return Font.VerticalTextAlignValue.Subscript;
587 return Font.VerticalTextAlignValue.Baseline;
596 private Font.SchemeValue ParseSchemeValue(
string value)
598 switch (value.ToLowerInvariant())
601 return Font.SchemeValue.Major;
603 return Font.SchemeValue.Minor;
605 return Font.SchemeValue.None;
616 switch (value.ToLowerInvariant())
618 case "halfwidthkatakana":
636 switch (value.ToLowerInvariant())
656 sealed class PhoneticInfo
661 public string Value {
get;
private set; }
665 public int StartIndex {
get;
private set; }
669 public int Length {
get;
private set; }
677 public PhoneticInfo(
string value,
string start,
string end)
680 StartIndex = ParserUtils.ParseInt(start);
681 Length = ParserUtils.ParseInt(end) - StartIndex;