blob: 7f43d9c7ea18aef098d8bb285bfdd857d687ff03 [file] [log] [blame]
/***************************************************************************
Copyright (c) Microsoft Corporation 2012-2015.
This code is licensed using the Microsoft Public License (Ms-PL). The text of the license can be found here:
http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx
Published at http://OpenXmlDeveloper.org
Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
Developer: Eric White
Blog: http://www.ericwhite.com
Twitter: @EricWhiteDev
Email: eric@ericwhite.com
***************************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows.Forms;
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<string>();
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";
}
}