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; }
75 public string DocumentType {
get {
return @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"; } }
83 public FormattedSharedStringsReader()
85 phoneticsInfo =
new List<PhoneticInfo>();
86 SharedStrings =
new List<string>();
87 FormattedTexts =
new Dictionary<string, FormattedText>();
99 public void Init(Stream stream, Workbook workbook, IOptions readerOptions, Action<Stream, Workbook, string, IOptions, int?> inlinePluginHandler)
101 this.stream = stream;
102 this.workbook = workbook;
103 if (readerOptions is ITextOptions options)
105 this.capturePhoneticCharacters = options.EnforcePhoneticCharacterImport;
107 this.InlinePluginHandler = inlinePluginHandler;
114 public void Execute()
120 bool hasFormattedText =
false;
121 StringBuilder sb =
new StringBuilder();
122 using (XmlReader reader = XmlReader.Create(stream, XmlStreamUtils.CreateSettings()))
124 while (reader.Read())
126 if (!XmlStreamUtils.IsElement(reader,
"si"))
132 phoneticsInfo.Clear();
135 using (XmlReader siReader = reader.ReadSubtree())
138 formattedText = ProcessSharedStringItem(siReader, ref sb);
142 if (capturePhoneticCharacters)
144 textValue = ProcessPhoneticCharacters(sb);
145 formattedText.OverridePlainText(textValue);
147 else if (formattedText !=
null &&
string.IsNullOrEmpty(formattedText.
PlainText) && sb.Length > 0)
149 textValue = sb.ToString();
150 formattedText.OverridePlainText(textValue);
154 textValue = sb.ToString();
157 if (formattedText !=
null)
159 string key = PlugInUUID.SharedStringsReader + textValue;
160 SharedStrings.Add(key);
161 FormattedTexts[key] = formattedText;
162 hasFormattedText =
true;
166 SharedStrings.Add(textValue);
170 InlinePluginHandler?.Invoke(stream, Workbook, PlugInUUID.SharedStringsInlineReader, Options,
null);
171 if (hasFormattedText)
173 Workbook.AuxiliaryData.SetData(PlugInUUID.SharedStringsReader, AUXILIARY_DATA_ID, FormattedTexts);
179 throw new IOException(
"The XML entry could not be read from the " + nameof(stream) +
". Please see the inner exception:", ex);
189 private FormattedText ProcessSharedStringItem(XmlReader siReader, ref StringBuilder sb)
191 bool hasRuns =
false;
192 bool hasFormattedContent =
false;
195 while (siReader.Read())
197 if (siReader.NodeType != XmlNodeType.Element)
200 if (XmlStreamUtils.IsElement(siReader,
"r"))
203 hasFormattedContent =
true;
204 if (formattedText ==
null)
206 using (XmlReader runReader = siReader.ReadSubtree())
209 ProcessTextRun(runReader, formattedText, ref sb);
212 else if (XmlStreamUtils.IsElement(siReader,
"rPh"))
214 hasFormattedContent =
true;
215 if (formattedText ==
null)
217 using (XmlReader rPhReader = siReader.ReadSubtree())
220 ProcessPhoneticRun(rPhReader, formattedText);
223 else if (XmlStreamUtils.IsElement(siReader,
"phoneticPr"))
225 hasFormattedContent =
true;
226 if (formattedText ==
null)
228 ProcessPhoneticProperties(siReader, formattedText);
229 using (siReader.ReadSubtree()) { }
231 else if (XmlStreamUtils.IsElement(siReader,
"t") && !hasRuns)
235 using (XmlReader tReader = siReader.ReadSubtree())
238 text = tReader.ReadElementContentAsString();
244 return hasFormattedContent ? formattedText :
null;
253 private void ProcessTextRun(XmlReader runReader,
FormattedText formattedText, ref StringBuilder sb)
255 Font fontStyle =
null;
258 while (runReader.Read())
260 if (runReader.NodeType != XmlNodeType.Element)
263 if (XmlStreamUtils.IsElement(runReader,
"rPr"))
265 using (XmlReader rPrReader = runReader.ReadSubtree())
268 fontStyle = ParseRunProperties(rPrReader);
271 else if (XmlStreamUtils.IsElement(runReader,
"t"))
273 using (XmlReader tReader = runReader.ReadSubtree())
276 text = tReader.ReadElementContentAsString();
282 if (!
string.IsNullOrEmpty(text))
284 formattedText.
AddRun(text, fontStyle);
293 private Font ParseRunProperties(XmlReader rPrReader)
295 Font font =
new Font();
297 while (rPrReader.Read())
299 if (rPrReader.NodeType != XmlNodeType.Element)
302 string nodeName = rPrReader.LocalName;
304 if (nodeName.Equals(
"rFont", StringComparison.OrdinalIgnoreCase))
306 font.Name = rPrReader.GetAttribute(
"val");
308 else if (nodeName.Equals(
"charset", StringComparison.OrdinalIgnoreCase))
310 string val = rPrReader.GetAttribute(
"val");
311 if (!
string.IsNullOrEmpty(val))
312 font.Charset = (Font.CharsetValue)ParserUtils.ParseInt(val);
314 else if (nodeName.Equals(
"family", StringComparison.OrdinalIgnoreCase))
316 string val = rPrReader.GetAttribute(
"val");
317 if (!
string.IsNullOrEmpty(val))
318 font.Family = (Font.FontFamilyValue)ParserUtils.ParseInt(val);
320 else if (nodeName.Equals(
"b", StringComparison.OrdinalIgnoreCase))
324 else if (nodeName.Equals(
"i", StringComparison.OrdinalIgnoreCase))
328 else if (nodeName.Equals(
"strike", StringComparison.OrdinalIgnoreCase))
332 else if (nodeName.Equals(
"outline", StringComparison.OrdinalIgnoreCase))
336 else if (nodeName.Equals(
"shadow", StringComparison.OrdinalIgnoreCase))
340 else if (nodeName.Equals(
"condense", StringComparison.OrdinalIgnoreCase))
342 font.Condense =
true;
344 else if (nodeName.Equals(
"extend", StringComparison.OrdinalIgnoreCase))
348 else if (nodeName.Equals(
"color", StringComparison.OrdinalIgnoreCase))
350 font.ColorValue = ParseColor(rPrReader);
352 else if (nodeName.Equals(
"sz", StringComparison.OrdinalIgnoreCase))
354 string val = rPrReader.GetAttribute(
"val");
355 if (!
string.IsNullOrEmpty(val))
356 font.Size = ParserUtils.ParseFloat(val);
358 else if (nodeName.Equals(
"u", StringComparison.OrdinalIgnoreCase))
360 string val = rPrReader.GetAttribute(
"val");
361 font.Underline =
string.IsNullOrEmpty(val) ? Font.UnderlineValue.Single : ParseUnderlineValue(val);
363 else if (nodeName.Equals(
"vertAlign", StringComparison.OrdinalIgnoreCase))
365 string val = rPrReader.GetAttribute(
"val");
366 if (!
string.IsNullOrEmpty(val))
367 font.VerticalAlign = ParseVerticalAlignValue(val);
369 else if (nodeName.Equals(
"scheme", StringComparison.OrdinalIgnoreCase))
371 string val = rPrReader.GetAttribute(
"val");
372 if (!
string.IsNullOrEmpty(val))
373 font.Scheme = ParseSchemeValue(val);
385 private Color ParseColor(XmlReader reader)
387 string autoValue = reader.GetAttribute(
"auto");
388 string indexedValue = reader.GetAttribute(
"indexed");
389 string rgbValue = reader.GetAttribute(
"rgb");
390 string themeValue = reader.GetAttribute(
"theme");
391 string systemValue = reader.GetAttribute(
"system");
392 string tintValue = reader.GetAttribute(
"tint");
396 if (!
string.IsNullOrEmpty(autoValue))
397 color = Color.CreateAuto();
398 else if (!
string.IsNullOrEmpty(indexedValue))
399 color = Color.CreateIndexed(ParserUtils.ParseInt(indexedValue));
400 else if (!
string.IsNullOrEmpty(rgbValue))
401 color = Color.CreateRgb(rgbValue);
402 else if (!
string.IsNullOrEmpty(themeValue))
403 color = Color.CreateTheme(ParserUtils.ParseInt(themeValue));
404 else if (!
string.IsNullOrEmpty(systemValue))
405 color = Color.CreateSystem(SystemColor.MapStringToValue(systemValue));
407 if (color !=
null && !
string.IsNullOrEmpty(tintValue))
408 color.Tint = ParserUtils.ParseFloat(tintValue);
418 private void ProcessPhoneticRun(XmlReader rPhReader,
FormattedText formattedText)
420 string startBase = rPhReader.GetAttribute(
"sb");
421 string endBase = rPhReader.GetAttribute(
"eb");
424 while (rPhReader.Read())
426 if (rPhReader.NodeType != XmlNodeType.Element)
429 if (XmlStreamUtils.IsElement(rPhReader,
"t"))
431 using (XmlReader tReader = rPhReader.ReadSubtree())
434 text = tReader.ReadElementContentAsString();
439 if (!
string.IsNullOrEmpty(text) && !
string.IsNullOrEmpty(startBase) && !
string.IsNullOrEmpty(endBase))
441 uint sb = (uint)ParserUtils.ParseInt(startBase);
442 uint eb = (uint)ParserUtils.ParseInt(endBase);
445 if (capturePhoneticCharacters)
447 phoneticsInfo.Add(
new PhoneticInfo(text, startBase, endBase));
457 private void ProcessPhoneticProperties(XmlReader reader,
FormattedText formattedText)
459 string typeValue = reader.GetAttribute(
"type");
460 string alignmentValue = reader.GetAttribute(
"alignment");
463 if (!
string.IsNullOrEmpty(typeValue))
464 type = ParsePhoneticType(typeValue);
467 if (!
string.IsNullOrEmpty(alignmentValue))
468 alignment = ParsePhoneticAlignment(alignmentValue);
478 private string ProcessPhoneticCharacters(StringBuilder sb)
481 StringBuilder sb2 =
new StringBuilder();
482 int currentTextIndex = 0;
483 foreach (PhoneticInfo info
in phoneticsInfo)
485 sb2.Append(text.Substring(currentTextIndex, info.StartIndex + info.Length - currentTextIndex));
486 sb2.Append(
'(').Append(info.Value).Append(
')');
487 currentTextIndex = info.StartIndex + info.Length;
489 sb2.Append(text.Substring(currentTextIndex));
491 return sb2.ToString();
497 private Font.UnderlineValue ParseUnderlineValue(
string value)
499 switch (value.ToLowerInvariant())
502 return Font.UnderlineValue.Double;
503 case "singleaccounting":
504 return Font.UnderlineValue.SingleAccounting;
505 case "doubleaccounting":
506 return Font.UnderlineValue.DoubleAccounting;
508 return Font.UnderlineValue.Single;
515 private Font.VerticalTextAlignValue ParseVerticalAlignValue(
string value)
517 switch (value.ToLowerInvariant())
520 return Font.VerticalTextAlignValue.Superscript;
522 return Font.VerticalTextAlignValue.Subscript;
524 return Font.VerticalTextAlignValue.Baseline;
531 private Font.SchemeValue ParseSchemeValue(
string value)
533 switch (value.ToLowerInvariant())
536 return Font.SchemeValue.Major;
538 return Font.SchemeValue.Minor;
540 return Font.SchemeValue.None;
549 switch (value.ToLowerInvariant())
551 case "halfwidthkatakana":
567 switch (value.ToLowerInvariant())
587 sealed class PhoneticInfo
592 public string Value {
get;
private set; }
596 public int StartIndex {
get;
private set; }
600 public int Length {
get;
private set; }
608 public PhoneticInfo(
string value,
string start,
string end)
611 StartIndex = ParserUtils.ParseInt(start);
612 Length = ParserUtils.ParseInt(end) - StartIndex;