// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Globalization; using System.Linq; using System.Text; using System.Xml.Linq; using DocumentFormat.OpenXml.Packaging; using System.IO; using OpenXmlPowerTools; namespace OpenXmlPowerTools { public class SmlDataRetriever { public static XElement RetrieveSheet(SmlDocument smlDoc, string sheetName) { using (MemoryStream ms = new MemoryStream()) { ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length); using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false)) { return RetrieveSheet(sDoc, sheetName); } } } public static XElement RetrieveSheet(string fileName, string sheetName) { using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false)) { return RetrieveSheet(sDoc, sheetName); } } public static XElement RetrieveSheet(SpreadsheetDocument sDoc, string sheetName) { var wbXdoc = sDoc.WorkbookPart.GetXDocument(); var sheet = wbXdoc .Root .Elements(S.sheets) .Elements(S.sheet) .FirstOrDefault(s => (string)s.Attribute("name") == sheetName); if (sheet == null) throw new ArgumentException("Invalid sheet name passed to RetrieveSheet", "sheetName"); var range = "A1:XFD1048576"; int leftColumn, topRow, rightColumn, bottomRow; XlsxTables.ParseRange(range, out leftColumn, out topRow, out rightColumn, out bottomRow); return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow); } public static XElement RetrieveRange(SmlDocument smlDoc, string sheetName, string range) { using (MemoryStream ms = new MemoryStream()) { ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length); using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false)) { return RetrieveRange(sDoc, sheetName, range); } } } public static XElement RetrieveRange(string fileName, string sheetName, string range) { using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false)) { return RetrieveRange(sDoc, sheetName, range); } } public static XElement RetrieveRange(SpreadsheetDocument sDoc, string sheetName, string range) { int leftColumn, topRow, rightColumn, bottomRow; XlsxTables.ParseRange(range, out leftColumn, out topRow, out rightColumn, out bottomRow); return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow); } public static XElement RetrieveRange(SmlDocument smlDoc, string sheetName, int leftColumn, int topRow, int rightColumn, int bottomRow) { using (MemoryStream ms = new MemoryStream()) { ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length); using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false)) { return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow); } } } public static XElement RetrieveRange(string fileName, string sheetName, int leftColumn, int topRow, int rightColumn, int bottomRow) { using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false)) { return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow); } } public static XElement RetrieveRange(SpreadsheetDocument sDoc, string sheetName, int leftColumn, int topRow, int rightColumn, int bottomRow) { var wbXdoc = sDoc.WorkbookPart.GetXDocument(); var sheet = wbXdoc .Root .Elements(S.sheets) .Elements(S.sheet) .FirstOrDefault(s => (string)s.Attribute("name") == sheetName); if (sheet == null) throw new ArgumentException("Invalid sheet name passed to RetrieveRange", "sheetName"); var rId = (string)sheet.Attribute(R.id); if (rId == null) throw new FileFormatException("Invalid spreadsheet"); var sheetPart = sDoc.WorkbookPart.GetPartById(rId); if (sheetPart == null) throw new FileFormatException("Invalid spreadsheet"); var shXDoc = sheetPart.GetXDocument(); if (sDoc.WorkbookPart.WorkbookStylesPart == null) throw new FileFormatException("Invalid spreadsheet. No WorkbookStylesPart."); var styleXDoc = sDoc.WorkbookPart.WorkbookStylesPart.GetXDocument(); // if there is no shared string table, sharedStringTable will be null // it will only be used if there is a cell type == "s", in which case, referencing this // part would indicate an invalid spreadsheet. SharedStringTablePart sharedStringTable = sDoc.WorkbookPart.SharedStringTablePart; FixUpCellsThatHaveNoRAtt(shXDoc); // assemble the transform var sheetData = shXDoc .Root .Elements(S.sheetData) .Elements(S.row) .Select(row => { // filter string ra = (string)row.Attribute("r"); if (ra == null) return null; int rowNbr; if (!int.TryParse(ra, out rowNbr)) return null; if (rowNbr < topRow) return null; if (rowNbr > bottomRow) return null; var cells = row .Elements(S.c) .Select(cell => { var cellAddress = (string)cell.Attribute("r"); if (cellAddress == null) throw new FileFormatException("Invalid spreadsheet - cell does not have r attribute."); var splitCellAddress = XlsxTables.SplitAddress(cellAddress); var columnAddress = splitCellAddress[0]; var columnIndex = XlsxTables.ColumnAddressToIndex(columnAddress); // filter if (columnIndex < leftColumn || columnIndex > rightColumn) return null; var cellType = (string)cell.Attribute("t"); string sharedString = null; if (cellType == "s") { int sharedStringIndex; string sharedStringBeforeParsing = (string)cell.Element(S.v); if (sharedStringBeforeParsing == null) sharedStringBeforeParsing = (string)cell.Elements(S._is).Elements(S.t).FirstOrDefault(); if (sharedStringBeforeParsing == null) throw new FileFormatException("Invalid document"); if (!int.TryParse(sharedStringBeforeParsing, out sharedStringIndex)) throw new FileFormatException("Invalid document"); XElement sharedStringElement = null; if (sharedStringTable == null) throw new FileFormatException("Invalid spreadsheet. Shared string, but no Shared String Part."); sharedStringElement = sharedStringTable .GetXDocument() .Root .Elements(S.si) .Skip(sharedStringIndex) .FirstOrDefault(); if (sharedStringElement == null) throw new FileFormatException("Invalid spreadsheet. Shared string reference not valid."); sharedString = sharedStringElement .Descendants(S.t) .StringConcatenate(e => (string)e); } if (sharedString != null) { XElement cellProps = GetCellProps_NotInTable(sDoc, styleXDoc, cell); string value = sharedString; string displayValue; string color = null; if (cellProps != null) displayValue = SmlCellFormatter.FormatCell( (string)cellProps.Attribute("formatCode"), value, out color); else displayValue = value; XElement newCell1 = new XElement("Cell", new XAttribute("Ref", (string)cell.Attribute("r")), new XAttribute("ColumnId", columnAddress), new XAttribute("ColumnNumber", columnIndex), cell.Attribute("f") != null ? new XAttribute("Formula", (string)cell.Attribute("f")) : null, cell.Attribute("s") != null ? new XAttribute("Style", (string)cell.Attribute("s")) : null, cell.Attribute("t") != null ? new XAttribute("Type", (string)cell.Attribute("t")) : null, cellProps, new XElement("Value", value), new XElement("DisplayValue", displayValue), color != null ? new XElement("DisplayColor", color) : null ); return newCell1; } else { var type = (string)cell.Attribute("t"); XElement value = new XElement("Value", cell.Value); if (type != null && type == "inlineStr") { type = "s"; } XAttribute typeAttr = null; if (type != null) typeAttr = new XAttribute("Type", type); XElement cellProps = GetCellProps_NotInTable(sDoc, styleXDoc, cell); string displayValue; string color = null; if (cellProps != null) displayValue = SmlCellFormatter.FormatCell( (string)cellProps.Attribute("formatCode"), cell.Value, out color); else displayValue = displayValue = SmlCellFormatter.FormatCell( (string)"General", cell.Value, out color); XElement newCell2 = new XElement("Cell", new XAttribute("Ref", (string)cell.Attribute("r")), new XAttribute("ColumnId", columnAddress), new XAttribute("ColumnNumber", columnIndex), typeAttr, cell.Attribute("f") != null ? new XAttribute("Formula", (string)cell.Attribute("f")) : null, cell.Attribute("s") != null ? new XAttribute("Style", (string)cell.Attribute("s")) : null, cellProps, value, new XElement("DisplayValue", displayValue), color != null ? new XElement("DisplayColor", color) : null); return newCell2; } }); XElement dataRow = new XElement("Row", row.Attribute("r") != null ? new XAttribute("RowNumber", (int)row.Attribute("r")) : null, cells); return dataRow; }); var dataProps = GetDataProps(shXDoc); XElement data = new XElement("Data", dataProps, sheetData); return data; } // Sometimes encounter cells that have no r attribute, so infer it if possible. // These are invalid spreadsheets, but attempt to get the data anyway. private static void FixUpCellsThatHaveNoRAtt(XDocument shXDoc) { // if there are any rows that have all cells with no r attribute, then fix them up var invalidRows = shXDoc .Descendants(S.row) .Where(r => ! r.Elements(S.c).Any(c => c.Attribute("r") != null)) .ToList(); foreach (var row in invalidRows) { var rowNumberStr = (string)row.Attribute("r"); var colNumber = 0; foreach (var cell in row.Elements(S.c)) { var newCellRef = XlsxTables.IndexToColumnAddress(colNumber) + rowNumberStr; cell.Add(new XAttribute("r", newCellRef)); } } // repeat iteratively until no further fixes can be made while (true) { var invalidCells = shXDoc .Descendants(S.c) .Where(c => c.Attribute("r") == null) .ToList(); bool didFixup = false; foreach (var cell in invalidCells) { var followingCell = cell.ElementsAfterSelf(S.c).FirstOrDefault(); if (followingCell != null) { var followingR = (string)followingCell.Attribute("r"); if (followingR != null) { var spl = XlsxTables.SplitAddress(followingR); var colIdxFollowing = XlsxTables.ColumnAddressToIndex(spl[0]); var newRef = XlsxTables.IndexToColumnAddress(colIdxFollowing - 1) + spl[1]; cell.Add(new XAttribute("r", newRef)); didFixup = true; } else { didFixup = FixUpBasedOnPrecedingCell(didFixup, cell); } } else { didFixup = FixUpBasedOnPrecedingCell(didFixup, cell); } } if (!didFixup) break; } } private static bool FixUpBasedOnPrecedingCell(bool didFixup, XElement cell) { XElement precedingCell = GetPrevousElement(cell); if (precedingCell != null) { var precedingR = (string)precedingCell.Attribute("r"); if (precedingR != null) { var spl = XlsxTables.SplitAddress(precedingR); var colIdxFollowing = XlsxTables.ColumnAddressToIndex(spl[0]); var newRef = XlsxTables.IndexToColumnAddress(colIdxFollowing + 1) + spl[1]; cell.Add(new XAttribute("r", newRef)); didFixup = true; } } return didFixup; } private static XElement GetPrevousElement(XElement element) { XElement previousElement = null; XNode currentNode = element; while (true) { if (currentNode.PreviousNode == null) return null; previousElement = currentNode.PreviousNode as XElement; if (previousElement != null) return previousElement; currentNode = currentNode.PreviousNode; } } public static XElement RetrieveTable(SmlDocument smlDoc, string sheetName, string tableName) { using (MemoryStream ms = new MemoryStream()) { ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length); using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false)) { return RetrieveTable(sDoc, tableName); } } } public static XElement RetrieveTable(string fileName, string tableName) { using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false)) { return RetrieveTable(sDoc, tableName); } } public static XElement RetrieveTable(SpreadsheetDocument sDoc, string tableName) { var table = sDoc.Table(tableName); if (table == null) throw new ArgumentException("Table not found", "tableName"); var styleXDoc = sDoc.WorkbookPart.WorkbookStylesPart.GetXDocument(); var r = table.Ref; int leftColumn, topRow, rightColumn, bottomRow; XlsxTables.ParseRange(r, out leftColumn, out topRow, out rightColumn, out bottomRow); var shXDoc = table.Parent.GetXDocument(); FixUpCellsThatHaveNoRAtt(shXDoc); // assemble the transform var columns = new XElement("Columns", table.TableColumns().Select(tc => { var colXElement = new XElement("Column", tc.Name != null ? new XAttribute("Name", tc.Name) : null, tc.UniqueName != null ? new XAttribute("UniqueName", tc.UniqueName) : null, new XAttribute("ColumnIndex", tc.ColumnIndex), new XAttribute("Id", tc.Id), tc.DataDxfId != null ? new XAttribute("DataDxfId", tc.DataDxfId) : null, tc.QueryTableFieldId != null ? new XAttribute("QueryTableFieldId", tc.QueryTableFieldId) : null); return colXElement; })); var dataProps = GetDataProps(shXDoc); var data = new XElement("Data", dataProps, table.TableRows().Select(tr => { int rowRef; if (!int.TryParse(tr.Row.RowId, out rowRef)) throw new FileFormatException("Invalid spreadsheet"); // filter if (rowRef < topRow || rowRef > bottomRow) return null; var cellData = tr.Row.Cells().Select(tc => { // filter var columnIndex = tc.ColumnIndex; if (columnIndex < leftColumn || columnIndex > rightColumn) return null; XElement cellProps = GetCellProps_InTable(sDoc, styleXDoc, table, tc); if (tc.SharedString != null) { string displayValue; string color = null; if (cellProps != null) displayValue = SmlCellFormatter.FormatCell( (string)cellProps.Attribute("formatCode"), tc.SharedString, out color); else displayValue = tc.SharedString; XElement newCell1 = new XElement("Cell", tc.CellElement != null ? new XAttribute("Ref", (string)tc.CellElement.Attribute("r")) : null, tc.ColumnAddress != null ? new XAttribute("ColumnId", tc.ColumnAddress) : null, new XAttribute("ColumnNumber", tc.ColumnIndex), tc.Type != null ? new XAttribute("Type", "s") : null, tc.Formula != null ? new XAttribute("Formula", tc.Formula) : null, tc.Style != null ? new XAttribute("Style", tc.Style) : null, cellProps, new XElement("Value", tc.SharedString), new XElement("DisplayValue", displayValue), color != null ? new XElement("DisplayColor", color) : null); return newCell1; } else { XAttribute type = null; if (tc.Type != null) { if (tc.Type == "inlineStr") type = new XAttribute("Type", "s"); else type = new XAttribute("Type", tc.Type); } string displayValue; string color = null; if (cellProps != null) displayValue = SmlCellFormatter.FormatCell( (string)cellProps.Attribute("formatCode"), tc.Value, out color); else displayValue = SmlCellFormatter.FormatCell( (string)"General", tc.Value, out color); XElement newCell = new XElement("Cell", tc.CellElement != null ? new XAttribute("Ref", (string)tc.CellElement.Attribute("r")) : null, tc.ColumnAddress != null ? new XAttribute("ColumnId", tc.ColumnAddress) : null, new XAttribute("ColumnNumber", tc.ColumnIndex), type, tc.Formula != null ? new XAttribute("Formula", tc.Formula) : null, tc.Style != null ? new XAttribute("Style", tc.Style) : null, cellProps, new XElement("Value", tc.Value), new XElement("DisplayValue", displayValue), color != null ? new XElement("DisplayColor", color) : null); return newCell; } }); var rowProps = GetRowProps(tr.Row.RowElement); var newRow = new XElement("Row", rowProps, new XAttribute("RowNumber", tr.Row.RowId), cellData); return newRow; })); XElement tableProps = GetTableProps(table); var tableXml = new XElement("Table", tableProps, table.TableName != null ? new XAttribute("TableName", table.TableName) : null, table.DisplayName != null ? new XAttribute("DisplayName", table.DisplayName) : null, table.Ref != null ? new XAttribute("Ref", table.Ref) : null, table.HeaderRowCount != null ? new XAttribute("HeaderRowCount", table.HeaderRowCount) : null, table.TotalsRowCount != null ? new XAttribute("TotalsRowCount", table.TotalsRowCount) : null, columns, data); return tableXml; } public static string[] SheetNames(SmlDocument smlDoc) { using (MemoryStream ms = new MemoryStream()) { ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length); using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false)) { return SheetNames(sDoc); } } } public static string[] SheetNames(string fileName) { using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false)) { return SheetNames(sDoc); } } public static string[] SheetNames(SpreadsheetDocument sDoc) { var workbookXDoc = sDoc.WorkbookPart.GetXDocument(); var sheetNames = workbookXDoc.Root.Elements(S.sheets).Elements(S.sheet).Attributes("name").Select(a => (string)a).ToArray(); return sheetNames; } public static string[] TableNames(SmlDocument smlDoc) { using (MemoryStream ms = new MemoryStream()) { ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length); using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false)) { return TableNames(sDoc); } } } public static string[] TableNames(string fileName) { using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false)) { return TableNames(sDoc); } } public static string[] TableNames(SpreadsheetDocument sDoc) { var workbookXDoc = sDoc.WorkbookPart.GetXDocument(); var sheets = workbookXDoc.Root.Elements(S.sheets).Elements(S.sheet); var tableNames = sheets.Select(sh => { var rId = (string)sh.Attribute(R.id); var sheetPart = sDoc.WorkbookPart.GetPartById(rId); var sheetXDoc = sheetPart.GetXDocument(); var tableParts = sheetXDoc.Root.Element(S.tableParts); if (tableParts == null) return new List(); var tableNames2 = tableParts .Elements(S.tablePart) .Select(tp => { var tpRId = (string)tp.Attribute(R.id); var tpart = sheetPart.GetPartById(tpRId); var tpxd = tpart.GetXDocument(); var name = (string)tpxd.Root.Attribute("name"); return name; }) .ToList(); return tableNames2; }) .SelectMany(m => m) .ToArray(); return tableNames; } private static XElement GetTableProps(Table table) { XElement tableProps = new XElement("TableProps"); XElement tableStyleInfo = table.TableStyleInfo; if (tableStyleInfo != null) { var newTableStyleInfo = TransformRemoveNamespace(tableStyleInfo); tableProps.Add(newTableStyleInfo); } if (!tableProps.HasElements && !tableProps.HasElements) tableProps = null; return tableProps; } private static XElement GetDataProps(XDocument shXDoc) { var sheetFormatPr = shXDoc.Root.Element(S.sheetFormatPr); if (sheetFormatPr != null && sheetFormatPr.Attribute("defaultColWidth") == null) sheetFormatPr.Add(new XAttribute("defaultColWidth", "9.25")); if (sheetFormatPr != null && sheetFormatPr.Attribute("defaultRowHeight") == null) sheetFormatPr.Add(new XAttribute("defaultRowHeight", "14.25")); var mergeCells = TransformRemoveNamespace(shXDoc.Root.Element(S.mergeCells)); var dataProps = new XElement("DataProps", TransformRemoveNamespace(sheetFormatPr), TransformRemoveNamespace(shXDoc.Root.Element(S.cols)), mergeCells); if (!dataProps.HasAttributes && !dataProps.HasElements) dataProps = null; return dataProps; } private static XElement GetRowProps(XElement rowElement) { var rowProps = new XElement("RowProps"); var ht = rowElement.Attribute("ht"); if (ht != null) rowProps.Add(ht); var dyDescent = rowElement.Attribute(x14ac + "dyDescent"); if (dyDescent != null) rowProps.Add(new XAttribute("dyDescent", (string)dyDescent)); if (!rowProps.HasAttributes && !rowProps.HasElements) rowProps = null; return rowProps; } private static XElement GetCellProps_NotInTable(SpreadsheetDocument sDoc, XDocument styleXDoc, XElement cell) { var cellProps = new XElement("CellProps"); var style = (int?)cell.Attribute("s"); if (style == null) return cellProps; var xf = styleXDoc .Root .Elements(S.cellXfs) .Elements(S.xf) .Skip((int)style) .FirstOrDefault(); var numFmtId = (int?)xf.Attribute("numFmtId"); if (numFmtId != null) AddNumFmtIdAndFormatCode(styleXDoc, cellProps, numFmtId); var masterXfId = (int?)xf.Attribute("xfId"); if (masterXfId != null) { var masterXf = styleXDoc .Root .Elements(S.cellStyleXfs) .Elements(S.xf) .Skip((int)masterXfId) .FirstOrDefault(); if (masterXf != null) AddFormattingToCellProps(styleXDoc, cellProps, masterXf); } AddFormattingToCellProps(styleXDoc, cellProps, xf); AugmentAndCleanUpProps(cellProps); if (!cellProps.HasElements && !cellProps.HasAttributes) return null; return cellProps; } private static XElement GetCellProps_InTable(SpreadsheetDocument sDoc, XDocument styleXDoc, Table table, Cell tc) { var style = tc.Style; if (style == null) return null; var colIdStr = tc.ColumnAddress; int colNbr = XlsxTables.ColumnAddressToIndex(colIdStr); TableColumn column = table.TableColumns().FirstOrDefault(z => z.ColumnNumber == colNbr); if (column == null) throw new FileFormatException("Invalid spreadsheet"); var cellProps = new XElement("CellProps"); var d = column.DataDxfId; if (d != null) { var dataDxf = styleXDoc .Root .Elements(S.dxfs) .Elements(S.dxf) .Skip((int)d) .FirstOrDefault(); if (dataDxf == null) throw new FileFormatException("Invalid spreadsheet"); var numFmt = dataDxf.Element(S.numFmt); if (numFmt != null) { var numFmtId = (int?)numFmt.Attribute("numFmtId"); if (numFmtId != null) cellProps.Add(new XAttribute("numFmtId", numFmtId)); var formatCode = (string)numFmt.Attribute("formatCode"); if (formatCode != null) cellProps.Add(new XAttribute("formatCode", formatCode)); } } var xf = styleXDoc.Root .Elements(S.cellXfs) .Elements(S.xf) .Skip((int)style) .FirstOrDefault(); if (xf == null) throw new FileFormatException("Invalid spreadsheet"); // if xf has different numFmtId, then replace the ones from the table definition var numFmtId2 = (int?)xf.Attribute("numFmtId"); if (numFmtId2 != null) AddNumFmtIdAndFormatCode(styleXDoc, cellProps, numFmtId2); var masterXfId = (int?)xf.Attribute("xfId"); if (masterXfId != null) { var masterXf = styleXDoc .Root .Elements(S.cellStyleXfs) .Elements(S.xf) .Skip((int)masterXfId) .FirstOrDefault(); if (masterXf != null) AddFormattingToCellProps(styleXDoc, cellProps, masterXf); } AddFormattingToCellProps(styleXDoc, cellProps, xf); AugmentAndCleanUpProps(cellProps); if (!cellProps.HasElements && !cellProps.HasAttributes) return null; return cellProps; } private static void AddNumFmtIdAndFormatCode(XDocument styleXDoc, XElement props, int? numFmtId) { var existingNumFmtId = props.Attribute("numFmtId"); if (existingNumFmtId != null) existingNumFmtId.Value = numFmtId.ToString(); else props.Add(new XAttribute("numFmtId", numFmtId)); var numFmt = styleXDoc .Root .Elements(S.numFmts) .Elements(S.numFmt) .FirstOrDefault(z => (int)z.Attribute("numFmtId") == numFmtId); if (numFmt == null) { var formatCode = GetFormatCodeFromFmtId((int)numFmtId); if (formatCode != null) { var existingFormatCode = props.Attribute("formatCode"); if (existingFormatCode != null) existingFormatCode.Value = formatCode; else props.Add(new XAttribute("formatCode", formatCode)); } } else { var formatCode = (string)numFmt.Attribute("formatCode"); if (formatCode != null) { var existingFormatCode = props.Attribute("formatCode"); if (existingFormatCode != null) existingFormatCode.Value = formatCode; else props.Add(new XAttribute("formatCode", formatCode)); } } } private static void AddFormattingToCellProps(XDocument styleXDoc, XElement props, XElement xf) { MoveBooleanAttribute(props, xf, "applyAlignment"); MoveBooleanAttribute(props, xf, "applyBorder"); MoveBooleanAttribute(props, xf, "applyFill"); MoveBooleanAttribute(props, xf, "applyFont"); MoveBooleanAttribute(props, xf, "applyNumberFormat"); int? borderId = (int?)xf.Attribute("borderId"); int? fillId = (int?)xf.Attribute("fillId"); int? fontId = (int?)xf.Attribute("fontId"); if (fontId != null) { var fontElement = styleXDoc .Root .Elements(S.fonts) .Elements(S.font) .Skip((int)fontId) .FirstOrDefault(); if (fontElement != null) { var newFontElement = (XElement)TransformRemoveNamespace(fontElement); AddOrReplaceElement(props, newFontElement); } } if (fillId != null) { var fillElement = styleXDoc .Root .Elements(S.fills) .Elements(S.fill) .Skip((int)fillId) .FirstOrDefault(); if (fillElement != null) { var newFillElement = (XElement)TransformRemoveNamespace(fillElement); AddOrReplaceElement(props, newFillElement); } } if (borderId != null) { var borderElement = styleXDoc .Root .Elements(S.borders) .Elements(S.border) .Skip((int)borderId) .FirstOrDefault(); if (borderElement != null) { var newborderElement = (XElement)TransformRemoveNamespace(borderElement); AddOrReplaceElement(props, newborderElement); } } if (xf.Element(S.alignment) != null) { var newAlignmentElement = (XElement)TransformRemoveNamespace(xf.Element(S.alignment)); AddOrReplaceElement(props, newAlignmentElement); } } private static void MoveBooleanAttribute(XElement props, XElement xf, XName attributeName) { bool attrValue = ConvertAttributeToBool(xf.Attribute(attributeName)); if (attrValue) { if (props.Attribute(attributeName) == null) props.Add(new XAttribute(attributeName, attrValue ? "1" : "0")); else props.Attribute(attributeName).Value = attrValue ? "1" : "0"; } } public static string[] IndexedColors = new string[] { "00000000", "00FFFFFF", "00FF0000", "0000FF00", "000000FF", "00FFFF00", "00FF00FF", "0000FFFF", "00000000", "00FFFFFF", "00FF0000", "0000FF00", "000000FF", "00FFFF00", "00FF00FF", "0000FFFF", "00800000", "00008000", "00000080", "00808000", "00800080", "00008080", "00C0C0C0", "00808080", "009999FF", "00993366", "00FFFFCC", "00CCFFFF", "00660066", "00FF8080", "000066CC", "00CCCCFF", "00000080", "00FF00FF", "00FFFF00", "0000FFFF", "00800080", "00800000", "00008080", "000000FF", "0000CCFF", "00CCFFFF", "00CCFFCC", "00FFFF99", "0099CCFF", "00FF99CC", "00CC99FF", "00FFCC99", "003366FF", "0033CCCC", "0099CC00", "00FFCC00", "00FF9900", "00FF6600", "00666699", "00969696", "00003366", "00339966", "00003300", "00333300", "00993300", "00993366", "00333399", "00333333", "System Foreground", "System Background", }; private static string[] FontFamilyList = new string[] { "Not applicable", "Roman", "Swiss", "Modern", "Script", "Decorative", }; private static void AugmentAndCleanUpProps(XElement props) { foreach (var color in props.Descendants("color").Where(c => c.Attribute("indexed") != null).ToList()) { var idx = (int)color.Attribute("indexed"); if (idx < IndexedColors.Length) { color.Add(new XAttribute("val", IndexedColors[idx])); } color.Attribute("indexed").Remove(); } foreach (var family in props.Descendants("family").ToList()) { var fam = (int?)family.Attribute("val"); if (fam != null) { if (fam < FontFamilyList.Length) { family.Attribute("val").Remove(); family.Add(new XAttribute("val", FontFamilyList[(int)fam])); } } } foreach (var border in props.Descendants("border").ToList()) { RemoveIfEmpty(border.Element("left")); RemoveIfEmpty(border.Element("right")); RemoveIfEmpty(border.Element("top")); RemoveIfEmpty(border.Element("bottom")); RemoveIfEmpty(border.Element("diagonal")); if (!border.HasAttributes && !border.HasElements) border.Remove(); } foreach (var fill in props.Descendants("fill").ToList()) { fill.Elements("patternFill").Where(pf => (string)pf.Attribute("patternType") == "none").Remove(); if (!fill.HasAttributes && !fill.HasElements) fill.Remove(); } } private static void RemoveIfEmpty(XElement xElement) { if (xElement == null) return; if (!xElement.HasAttributes && !xElement.HasElements) xElement.Remove(); } private static object TransformRemoveNamespace(XNode node) { if (node == null) return null; XElement element = node as XElement; if (element != null) { return new XElement(element.Name.LocalName, element.Attributes().Select(a => new XAttribute(a.Name.LocalName, (string)a)).OrderBy(a => (string)a.Name.LocalName), element.Nodes().Select(n => TransformRemoveNamespace(n))); } return node; } private static string GetFormatCodeFromFmtId(int fmtId) { switch (fmtId) { case 0: return "General"; case 1: return "0"; case 2: return "0.00"; case 3: return "#,##0"; case 4: return "#,##0.00"; case 9: return "0%"; case 10: return "0.00%"; case 11: return "0.00E+00"; case 12: return "# ?/?"; case 13: return "# ??/??"; case 14: return "mm-dd-yy"; case 15: return "d-mmm-yy"; case 16: return "d-mmm"; case 17: return "mmm-yy"; case 18: return "h:mm AM/PM"; case 19: return "h:mm:ss AM/PM"; case 20: return "h:mm"; case 21: return "h:mm:ss"; case 22: return "22 m/d/yy h:mm"; case 37: return "#,##0 ;(#,##0)"; case 38: return "#,##0 ;[Red](#,##0)"; case 39: return "#,##0.00;(#,##0.00)"; case 40: return "#,##0.00;[Red](#,##0.00)"; case 45: return "mm:ss"; case 46: return "[h]:mm:ss"; case 47: return "mmss.0"; case 48: return "##0.0E+0"; case 49: return "@"; default: return null; } } private static void AddOrReplaceElement(XElement props, XName childElementName, int value) { var existingElement = props.Element(childElementName); if (existingElement != null) existingElement.ReplaceWith(new XElement(childElementName, new XAttribute("Val", value))); else props.Add(new XElement(childElementName, new XAttribute("Val", value))); } private static void AddOrReplaceElement(XElement props, XName childElementName, string value) { var existingElement = props.Element(childElementName); if (existingElement != null) existingElement.ReplaceWith(new XElement(childElementName, new XAttribute("Val", value))); else props.Add(new XElement(childElementName, new XAttribute("Val", value))); } private static void AddOrReplaceElement(XElement props, XElement element) { var existingElement = props.Element(element.Name); if (existingElement != null) existingElement.ReplaceWith(element); else props.Add(element); } private static bool ConvertAttributeToBool(XAttribute xAttribute) { string applyNumberFormatStr = (string)xAttribute; bool returnValue = false; if (applyNumberFormatStr != null) { if (applyNumberFormatStr == "1") returnValue = true; if (applyNumberFormatStr.Substring(0, 1).ToUpper() == "T") returnValue = true; } return returnValue; } private static XNamespace x14ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; } }