using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Xml; using System.Xml.Linq; namespace FastExcel { /// /// Read and update xl/sharedStrings.xml file /// public class SharedStrings { //A dictionary is a lot faster than a list private Dictionary StringDictionary { get; set; } private Dictionary StringArray { get; set; } private bool SharedStringsExists { get; set; } private ZipArchive ZipArchive { get; set; } /// /// Is there any pending changes /// public bool PendingChanges { get; private set; } /// /// Is in read/write mode /// public bool ReadWriteMode { get; set; } internal SharedStrings(ZipArchive archive) { ZipArchive = archive; SharedStringsExists = true; if (!ZipArchive.Entries.Where(entry => entry.FullName == "xl/sharedStrings.xml").Any()) { StringDictionary = new Dictionary(); SharedStringsExists = false; return; } using (Stream stream = ZipArchive.GetEntry("xl/sharedStrings.xml").Open()) { if (stream == null) { StringDictionary = new Dictionary(); SharedStringsExists = false; return; } var document = XDocument.Load(stream); if (document == null) { StringDictionary = new Dictionary(); SharedStringsExists = false; return; } // int i = 0; // StringDictionary = document.Descendants().Where(d => d.Name.LocalName == "t").Select(e => e.Value).Select(XmlConvert.DecodeName).to // .ToDictionary(k => k, v => i++); int i = 0; StringDictionary = new Dictionary(); List StringList = new List(); StringList = document.Descendants().Where(d => d.Name.LocalName == "t").Select(e => XmlConvert.DecodeName(e.Value)).ToList(); foreach (string currentString in StringList) { if (!StringDictionary.ContainsKey(currentString)) StringDictionary.Add(currentString, i++); } } } internal int AddString(string stringValue) { if (StringDictionary.ContainsKey(stringValue)) { return StringDictionary[stringValue]; } else { PendingChanges = true; StringDictionary.Add(stringValue, StringDictionary.Count); // Clear String Array used for retrieval if (ReadWriteMode && StringArray != null) { StringArray.Add(StringDictionary.Count - 1, stringValue); } else { StringArray = null; } return StringDictionary.Count - 1; } } internal void Write() { // Only update if changes were made if (!PendingChanges) { return; } StreamWriter streamWriter = null; try { if (SharedStringsExists) { streamWriter = new StreamWriter(ZipArchive.GetEntry("xl/sharedStrings.xml").Open()); } else { streamWriter = new StreamWriter(ZipArchive.CreateEntry("xl/sharedStrings.xml").Open()); } // TODO instead of saving the headers then writing them back get position where the headers finish then write from there /* Note: the count attribute value is wrong, it is the number of times strings are used thoughout the workbook it is different to the unique count * but because this library is about speed and Excel does not seem to care I am not going to fix it because I would need to read the whole workbook */ streamWriter.Write(string.Format("" + "", StringDictionary.Count)); // Add Rows foreach (var stringValue in StringDictionary) { streamWriter.Write(string.Format("{0}", XmlConvert.EncodeName(stringValue.Key))); } //Add Footers streamWriter.Write(""); streamWriter.Flush(); } finally { streamWriter.Dispose(); PendingChanges = false; } } internal string GetString(string position) { if (int.TryParse(position, out int pos)) { return GetString(pos + 1); } else { // TODO: should I throw an error? this is a corrupted excel document return string.Empty; } } internal string GetString(int position) { if (StringArray == null) { StringArray = StringDictionary.ToDictionary(kv => kv.Value, kv => kv.Key); } return StringArray[position - 1]; } } }