29 [NanoXlsxPlugIn(PlugInUUID = PlugInUUID.SharedStringsReader, PlugInOrder = 1000)]
30 internal class FormattedSharedStringsReader : ISharedStringReader
35 internal static readonly
int AUXILIARY_DATA_ID = 854563987;
38 private bool capturePhoneticCharacters;
39 private readonly List<PhoneticInfo> phoneticsInfo;
40 private Stream stream;
41 private Workbook workbook;
52 public List<string> SharedStrings {
get;
private set; }
57 public Dictionary<string, FormattedText> FormattedTexts {
get;
private set; }
62 public Workbook Workbook {
get => workbook;
set => workbook = value; }
66 public IOptions Options {
get;
set; }
70 public Action<Stream, Workbook, string, IOptions, int?> InlinePluginHandler {
get;
set; }
77 public FormattedSharedStringsReader()
79 phoneticsInfo =
new List<PhoneticInfo>();
80 SharedStrings =
new List<string>();
81 FormattedTexts =
new Dictionary<string, FormattedText>();
93 public void Init(Stream stream, Workbook workbook, IOptions readerOptions, Action<Stream, Workbook, string, IOptions, int?> inlinePluginHandler)
96 this.workbook = workbook;
97 if (readerOptions is ITextOptions options)
99 this.capturePhoneticCharacters = options.EnforcePhoneticCharacterImport;
101 this.InlinePluginHandler = inlinePluginHandler;
108 public void Execute()
114 bool hasFormattedText =
false;
115 StringBuilder sb =
new StringBuilder();
116 using (XmlReader reader = XmlReader.Create(stream, XmlStreamUtils.CreateSettings()))
118 while (reader.Read())
120 if (!XmlStreamUtils.IsElement(reader,
"si"))
124 phoneticsInfo.Clear();
127 using (XmlReader siReader = reader.ReadSubtree())
130 formattedText = ProcessSharedStringItem(siReader, ref sb);
134 if (capturePhoneticCharacters)
136 textValue = ProcessPhoneticCharacters(sb);
137 formattedText.OverridePlainText(textValue);
139 else if (formattedText !=
null &&
string.IsNullOrEmpty(formattedText.
PlainText) && sb.Length > 0)
141 textValue = sb.ToString();
142 formattedText.OverridePlainText(textValue);
146 textValue = sb.ToString();
149 if (formattedText !=
null)
151 string key = PlugInUUID.SharedStringsReader + textValue;
152 SharedStrings.Add(key);
153 FormattedTexts[key] = formattedText;
154 hasFormattedText =
true;
158 SharedStrings.Add(textValue);
162 InlinePluginHandler?.Invoke(stream, Workbook, PlugInUUID.SharedStringsInlineReader, Options,
null);
163 if (hasFormattedText)
165 Workbook.AuxiliaryData.SetData(PlugInUUID.SharedStringsReader, AUXILIARY_DATA_ID, FormattedTexts);
171 throw new IOException(
"The XML entry could not be read from the " + nameof(stream) +
". Please see the inner exception:", ex);
181 private FormattedText ProcessSharedStringItem(XmlReader siReader, ref StringBuilder sb)
183 bool hasRuns =
false;
184 bool hasFormattedContent =
false;
187 while (siReader.Read())
189 if (siReader.NodeType != XmlNodeType.Element)
192 if (XmlStreamUtils.IsElement(siReader,
"r"))
195 hasFormattedContent =
true;
196 if (formattedText ==
null)
198 using (XmlReader runReader = siReader.ReadSubtree())
201 ProcessTextRun(runReader, formattedText, ref sb);
204 else if (XmlStreamUtils.IsElement(siReader,
"rPh"))
206 hasFormattedContent =
true;
207 if (formattedText ==
null)
209 using (XmlReader rPhReader = siReader.ReadSubtree())
212 ProcessPhoneticRun(rPhReader, formattedText);
215 else if (XmlStreamUtils.IsElement(siReader,
"phoneticPr"))
217 hasFormattedContent =
true;
218 if (formattedText ==
null)
220 ProcessPhoneticProperties(siReader, formattedText);
221 using (siReader.ReadSubtree()) { }
223 else if (XmlStreamUtils.IsElement(siReader,
"t") && !hasRuns)
227 using (XmlReader tReader = siReader.ReadSubtree())
230 text = tReader.ReadElementContentAsString();
236 return hasFormattedContent ? formattedText :
null;
245 private void ProcessTextRun(XmlReader runReader,
FormattedText formattedText, ref StringBuilder sb)
247 Font fontStyle =
null;
250 while (runReader.Read())
252 if (runReader.NodeType != XmlNodeType.Element)
255 if (XmlStreamUtils.IsElement(runReader,
"rPr"))
257 using (XmlReader rPrReader = runReader.ReadSubtree())
260 fontStyle = ParseRunProperties(rPrReader);
263 else if (XmlStreamUtils.IsElement(runReader,
"t"))
265 using (XmlReader tReader = runReader.ReadSubtree())
268 text = tReader.ReadElementContentAsString();
274 if (!
string.IsNullOrEmpty(text))
276 formattedText.
AddRun(text, fontStyle);
285 private Font ParseRunProperties(XmlReader rPrReader)
287 Font font =
new Font();
289 while (rPrReader.Read())
291 if (rPrReader.NodeType != XmlNodeType.Element)
294 string nodeName = rPrReader.LocalName;
296 if (nodeName.Equals(
"rFont", StringComparison.OrdinalIgnoreCase))
298 font.Name = rPrReader.GetAttribute(
"val");
300 else if (nodeName.Equals(
"charset", StringComparison.OrdinalIgnoreCase))
302 string val = rPrReader.GetAttribute(
"val");
303 if (!
string.IsNullOrEmpty(val))
304 font.Charset = (Font.CharsetValue)ParserUtils.ParseInt(val);
306 else if (nodeName.Equals(
"family", StringComparison.OrdinalIgnoreCase))
308 string val = rPrReader.GetAttribute(
"val");
309 if (!
string.IsNullOrEmpty(val))
310 font.Family = (Font.FontFamilyValue)ParserUtils.ParseInt(val);
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(rPrReader);
344 else if (nodeName.Equals(
"sz", StringComparison.OrdinalIgnoreCase))
346 string val = rPrReader.GetAttribute(
"val");
347 if (!
string.IsNullOrEmpty(val))
348 font.Size = ParserUtils.ParseFloat(val);
350 else if (nodeName.Equals(
"u", StringComparison.OrdinalIgnoreCase))
352 string val = rPrReader.GetAttribute(
"val");
353 font.Underline =
string.IsNullOrEmpty(val) ? Font.UnderlineValue.Single : ParseUnderlineValue(val);
355 else if (nodeName.Equals(
"vertAlign", StringComparison.OrdinalIgnoreCase))
357 string val = rPrReader.GetAttribute(
"val");
358 if (!
string.IsNullOrEmpty(val))
359 font.VerticalAlign = ParseVerticalAlignValue(val);
361 else if (nodeName.Equals(
"scheme", StringComparison.OrdinalIgnoreCase))
363 string val = rPrReader.GetAttribute(
"val");
364 if (!
string.IsNullOrEmpty(val))
365 font.Scheme = ParseSchemeValue(val);
377 private Color ParseColor(XmlReader reader)
379 string autoValue = reader.GetAttribute(
"auto");
380 string indexedValue = reader.GetAttribute(
"indexed");
381 string rgbValue = reader.GetAttribute(
"rgb");
382 string themeValue = reader.GetAttribute(
"theme");
383 string systemValue = reader.GetAttribute(
"system");
384 string tintValue = reader.GetAttribute(
"tint");
388 if (!
string.IsNullOrEmpty(autoValue))
389 color = Color.CreateAuto();
390 else if (!
string.IsNullOrEmpty(indexedValue))
391 color = Color.CreateIndexed(ParserUtils.ParseInt(indexedValue));
392 else if (!
string.IsNullOrEmpty(rgbValue))
393 color = Color.CreateRgb(rgbValue);
394 else if (!
string.IsNullOrEmpty(themeValue))
395 color = Color.CreateTheme(ParserUtils.ParseInt(themeValue));
396 else if (!
string.IsNullOrEmpty(systemValue))
397 color = Color.CreateSystem(SystemColor.MapStringToValue(systemValue));
399 if (color !=
null && !
string.IsNullOrEmpty(tintValue))
400 color.Tint = ParserUtils.ParseFloat(tintValue);
410 private void ProcessPhoneticRun(XmlReader rPhReader,
FormattedText formattedText)
412 string startBase = rPhReader.GetAttribute(
"sb");
413 string endBase = rPhReader.GetAttribute(
"eb");
416 while (rPhReader.Read())
418 if (rPhReader.NodeType != XmlNodeType.Element)
421 if (XmlStreamUtils.IsElement(rPhReader,
"t"))
423 using (XmlReader tReader = rPhReader.ReadSubtree())
426 text = tReader.ReadElementContentAsString();
431 if (!
string.IsNullOrEmpty(text) && !
string.IsNullOrEmpty(startBase) && !
string.IsNullOrEmpty(endBase))
433 uint sb = (uint)ParserUtils.ParseInt(startBase);
434 uint eb = (uint)ParserUtils.ParseInt(endBase);
437 if (capturePhoneticCharacters)
439 phoneticsInfo.Add(
new PhoneticInfo(text, startBase, endBase));
449 private void ProcessPhoneticProperties(XmlReader reader,
FormattedText formattedText)
451 string typeValue = reader.GetAttribute(
"type");
452 string alignmentValue = reader.GetAttribute(
"alignment");
455 if (!
string.IsNullOrEmpty(typeValue))
456 type = ParsePhoneticType(typeValue);
459 if (!
string.IsNullOrEmpty(alignmentValue))
460 alignment = ParsePhoneticAlignment(alignmentValue);
470 private string ProcessPhoneticCharacters(StringBuilder sb)
473 StringBuilder sb2 =
new StringBuilder();
474 int currentTextIndex = 0;
475 foreach (PhoneticInfo info
in phoneticsInfo)
477 sb2.Append(text.Substring(currentTextIndex, info.StartIndex + info.Length - currentTextIndex));
478 sb2.Append(
'(').Append(info.Value).Append(
')');
479 currentTextIndex = info.StartIndex + info.Length;
481 sb2.Append(text.Substring(currentTextIndex));
483 return sb2.ToString();
489 private Font.UnderlineValue ParseUnderlineValue(
string value)
491 switch (value.ToLowerInvariant())
494 return Font.UnderlineValue.Double;
495 case "singleaccounting":
496 return Font.UnderlineValue.SingleAccounting;
497 case "doubleaccounting":
498 return Font.UnderlineValue.DoubleAccounting;
500 return Font.UnderlineValue.Single;
507 private Font.VerticalTextAlignValue ParseVerticalAlignValue(
string value)
509 switch (value.ToLowerInvariant())
512 return Font.VerticalTextAlignValue.Superscript;
514 return Font.VerticalTextAlignValue.Subscript;
516 return Font.VerticalTextAlignValue.Baseline;
523 private Font.SchemeValue ParseSchemeValue(
string value)
525 switch (value.ToLowerInvariant())
528 return Font.SchemeValue.Major;
530 return Font.SchemeValue.Minor;
532 return Font.SchemeValue.None;
541 switch (value.ToLowerInvariant())
543 case "halfwidthkatakana":
559 switch (value.ToLowerInvariant())
579 sealed class PhoneticInfo
584 public string Value {
get;
private set; }
588 public int StartIndex {
get;
private set; }
592 public int Length {
get;
private set; }
600 public PhoneticInfo(
string value,
string start,
string end)
603 StartIndex = ParserUtils.ParseInt(start);
604 Length = ParserUtils.ParseInt(end) - StartIndex;