| /******************************************************************************* |
| * You may amend and distribute as you like, but don't remove this header! |
| * |
| * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. |
| * See http://www.codeplex.com/EPPlus for details. |
| * |
| * Copyright (C) 2011 Jan K�llman |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| * See the GNU Lesser General Public License for more details. |
| * |
| * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php |
| * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html |
| * |
| * All code and executables are provided "as is" with no warranty either express or implied. |
| * The author accepts no liability for any damage or loss of business that this product may cause. |
| * |
| * Code change notes: |
| * |
| * Author Change Date |
| * ****************************************************************************** |
| * Jan K�llman Initial Release 2009-10-01 |
| * Jan K�llman License changed GPL-->LGPL 2011-12-27 |
| *******************************************************************************/ |
| |
| using System; |
| using System.Collections.Generic; |
| using System.Collections.Immutable; |
| using System.Linq; |
| using System.Xml; |
| using OfficeOpenXml.ConditionalFormatting; |
| using OfficeOpenXml.Style; |
| using OfficeOpenXml.Style.Dxf; |
| using OfficeOpenXml.Style.XmlAccess; |
| |
| namespace OfficeOpenXml; |
| |
| /// <summary> |
| /// Containts all shared cell styles for a workbook |
| /// </summary> |
| public sealed class ExcelStyles : XmlHelper { |
| private const string _numberFormatsPath = "d:styleSheet/d:numFmts"; |
| private const string _fontsPath = "d:styleSheet/d:fonts"; |
| private const string _fillsPath = "d:styleSheet/d:fills"; |
| private const string _bordersPath = "d:styleSheet/d:borders"; |
| private const string _cellStyleXfsPath = "d:styleSheet/d:cellStyleXfs"; |
| private const string _cellXfsPath = "d:styleSheet/d:cellXfs"; |
| private const string _cellStylesPath = "d:styleSheet/d:cellStyles"; |
| private const string _dxfsPath = "d:styleSheet/d:dxfs"; |
| |
| //internal Dictionary<int, ExcelXfs> Styles = new Dictionary<int, ExcelXfs>(); |
| private readonly XmlDocument _styleXml; |
| private readonly ExcelWorkbook _wb; |
| private readonly XmlNamespaceManager _nameSpaceManager; |
| internal int _nextDfxNumFmtID = 164; |
| |
| protected override ImmutableArray<string> SchemaNodeOrder { get; } = [ |
| "numFmts", |
| "fonts", |
| "fills", |
| "borders", |
| "cellStyleXfs", |
| "cellXfs", |
| "cellStyles", |
| "dxfs", |
| ]; |
| |
| internal ExcelStyles(XmlNamespaceManager nameSpaceManager, XmlDocument xml, ExcelWorkbook wb) |
| : base(nameSpaceManager, xml) { |
| _styleXml = xml; |
| _wb = wb; |
| _nameSpaceManager = nameSpaceManager; |
| LoadFromDocument(); |
| } |
| |
| /// <summary> |
| /// Loads the style XML to memory |
| /// </summary> |
| private void LoadFromDocument() { |
| //NumberFormats |
| ExcelNumberFormatXml.AddBuildIn(NameSpaceManager, NumberFormats); |
| XmlNode numNode = _styleXml.SelectSingleNode(_numberFormatsPath, _nameSpaceManager); |
| if (numNode != null) { |
| foreach (XmlNode n in numNode) { |
| ExcelNumberFormatXml nf = new ExcelNumberFormatXml(_nameSpaceManager, n); |
| NumberFormats.Add(nf.Id, nf); |
| if (nf.NumFmtId >= NumberFormats.NextId) { |
| NumberFormats.NextId = nf.NumFmtId + 1; |
| } |
| } |
| } |
| |
| //Fonts |
| XmlNode fontNode = _styleXml.SelectSingleNode(_fontsPath, _nameSpaceManager); |
| foreach (XmlNode n in fontNode) { |
| ExcelFontXml f = new ExcelFontXml(_nameSpaceManager, n); |
| Fonts.Add(f.Id, f); |
| } |
| |
| //Fills |
| XmlNode fillNode = _styleXml.SelectSingleNode(_fillsPath, _nameSpaceManager); |
| foreach (XmlNode n in fillNode) { |
| ExcelFillXml f; |
| if (n.FirstChild != null && n.FirstChild.LocalName == "gradientFill") { |
| f = new ExcelGradientFillXml(_nameSpaceManager, n); |
| } else { |
| f = new(_nameSpaceManager, n); |
| } |
| Fills.Add(f.Id, f); |
| } |
| |
| //Borders |
| XmlNode borderNode = _styleXml.SelectSingleNode(_bordersPath, _nameSpaceManager); |
| foreach (XmlNode n in borderNode) { |
| ExcelBorderXml b = new ExcelBorderXml(_nameSpaceManager, n); |
| Borders.Add(b.Id, b); |
| } |
| |
| //cellStyleXfs |
| XmlNode styleXfsNode = _styleXml.SelectSingleNode(_cellStyleXfsPath, _nameSpaceManager); |
| if (styleXfsNode != null) { |
| foreach (XmlNode n in styleXfsNode) { |
| ExcelXfs item = new ExcelXfs(_nameSpaceManager, n, this); |
| CellStyleXfs.Add(item.Id, item); |
| } |
| } |
| |
| XmlNode styleNode = _styleXml.SelectSingleNode(_cellXfsPath, _nameSpaceManager); |
| for (int i = 0; i < styleNode.ChildNodes.Count; i++) { |
| XmlNode n = styleNode.ChildNodes[i]; |
| ExcelXfs item = new ExcelXfs(_nameSpaceManager, n, this); |
| CellXfs.Add(item.Id, item); |
| } |
| |
| //cellStyle |
| XmlNode namedStyleNode = _styleXml.SelectSingleNode(_cellStylesPath, _nameSpaceManager); |
| if (namedStyleNode != null) { |
| foreach (XmlNode n in namedStyleNode) { |
| ExcelNamedStyleXml item = new ExcelNamedStyleXml(_nameSpaceManager, n, this); |
| NamedStyles.Add(item.Name, item); |
| } |
| } |
| |
| //dxfsPath |
| XmlNode dxfsNode = _styleXml.SelectSingleNode(_dxfsPath, _nameSpaceManager); |
| if (dxfsNode != null) { |
| foreach (XmlNode x in dxfsNode) { |
| ExcelDxfStyleConditionalFormatting item = new ExcelDxfStyleConditionalFormatting( |
| _nameSpaceManager, |
| x, |
| this); |
| Dxfs.Add(item.Id, item); |
| } |
| } |
| } |
| |
| internal ExcelStyle GetStyleObject(int id, int positionId, string address) { |
| if (id < 0) { |
| id = 0; |
| } |
| return new(this, PropertyChange, positionId, address, id); |
| } |
| |
| /// <summary> |
| /// Handels changes of properties on the style objects |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| /// <returns></returns> |
| internal int PropertyChange(StyleBase sender, StyleChangeEventArgs e) { |
| var address = new ExcelAddressBase(e.Address); |
| var ws = _wb.Worksheets[e.PositionID]; |
| Dictionary<int, int> styleCashe = new Dictionary<int, int>(); |
| //Set single address |
| SetStyleAddress(sender, e, address, ws, ref styleCashe); |
| if (address.Addresses != null) { |
| //Handle multiaddresses |
| foreach (var innerAddress in address.Addresses) { |
| SetStyleAddress(sender, e, innerAddress, ws, ref styleCashe); |
| } |
| } |
| return 0; |
| } |
| |
| private void SetStyleAddress( |
| StyleBase sender, |
| StyleChangeEventArgs e, |
| ExcelAddressBase address, |
| ExcelWorksheet ws, |
| ref Dictionary<int, int> styleCashe) { |
| if (address.Start.Column == 0 || address.Start.Row == 0) { |
| throw (new("error address")); |
| } |
| //Columns |
| if (address.Start.Row == 1 && address.End.Row == ExcelPackage.MaxRows) { |
| ExcelColumn column; |
| int col = address.Start.Column, |
| row = 0; |
| //Get the startcolumn |
| if (!ws._values.Exists(0, address.Start.Column)) { |
| column = ws.Column(address.Start.Column); |
| } else { |
| column = (ExcelColumn)ws._values.GetValue(0, address.Start.Column); |
| } |
| |
| while (column.ColumnMin <= address.End.Column) { |
| if (column.ColumnMax > address.End.Column) { |
| var newCol = ws.CopyColumn(column, address.End.Column + 1, column.ColumnMax); |
| column.ColumnMax = address.End.Column; |
| } |
| var s = ws._styles.GetValue(0, column.ColumnMin); |
| if (styleCashe.ContainsKey(s)) { |
| ws.SetStyle(0, column.ColumnMin, styleCashe[s]); |
| } else { |
| ExcelXfs st = CellXfs[s]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(s, newId); |
| ws.SetStyle(0, column.ColumnMin, newId); |
| } |
| |
| //index++; |
| |
| if (!ws._values.NextCell(ref row, ref col) || row > 0) { |
| column._columnMax = address.End.Column; |
| break; |
| } |
| column = (ws._values.GetValue(0, col) as ExcelColumn); |
| } |
| |
| if (column._columnMax < address.End.Column) { |
| var newCol = ws.Column(column._columnMax + 1); |
| newCol._columnMax = address.End.Column; |
| |
| var s = ws._styles.GetValue(0, column.ColumnMin); |
| if (styleCashe.ContainsKey(s)) { |
| ws.SetStyle(0, column.ColumnMin, styleCashe[s]); |
| } else { |
| ExcelXfs st = CellXfs[s]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(s, newId); |
| ws.SetStyle(0, column.ColumnMin, newId); |
| } |
| |
| column._columnMax = address.End.Column; |
| } |
| |
| //Set for individual cells in the span. We loop all cells here since the cells are sorted with columns first. |
| var cse = new CellsStoreEnumerator<int>( |
| ws._styles, |
| 1, |
| address._fromCol, |
| address._toRow, |
| address._toCol); |
| while (cse.Next()) { |
| if (cse.Column >= address.Start.Column && cse.Column <= address.End.Column) { |
| if (styleCashe.ContainsKey(cse.Value)) { |
| ws.SetStyle(cse.Row, cse.Column, styleCashe[cse.Value]); |
| } else { |
| ExcelXfs st = CellXfs[cse.Value]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(cse.Value, newId); |
| cse.Value = newId; |
| //ws.SetStyle(cse.Row, cse.Column, newId); |
| } |
| } |
| } |
| |
| //Update cells with styled columns |
| cse = new(ws._styles, 1, 0, address._toRow, 0); |
| while (cse.Next()) { |
| for (int c = address._fromRow; c <= address._toCol; c++) { |
| if (!ws._styles.Exists(cse.Row, c)) { |
| if (styleCashe.ContainsKey(cse.Value)) { |
| ws.SetStyle(cse.Row, c, styleCashe[cse.Value]); |
| } else { |
| ExcelXfs st = CellXfs[cse.Value]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(cse.Value, newId); |
| ws.SetStyle(cse.Row, c, newId); |
| } |
| } |
| } |
| } |
| } |
| //Rows |
| else if (address.Start.Column == 1 && address.End.Column == ExcelPackage.MaxColumns) { |
| for (int rowNum = address.Start.Row; rowNum <= address.End.Row; rowNum++) { |
| var s = ws._styles.GetValue(rowNum, 0); |
| if (s == 0) { |
| //iterate all columns and set the row to the style of the last column |
| var cse = new CellsStoreEnumerator<int>(ws._styles, 0, 1, 0, ExcelPackage.MaxColumns); |
| while (cse.Next()) { |
| s = cse.Value; |
| var c = ws._values.GetValue(cse.Row, cse.Column) as ExcelColumn; |
| if (c != null && c.ColumnMax < ExcelPackage.MaxColumns) { |
| for (int col = c.ColumnMin; col < c.ColumnMax; col++) { |
| if (!ws._styles.Exists(rowNum, col)) { |
| ws._styles.SetValue(rowNum, col, s); |
| } |
| } |
| } |
| } |
| ws.SetStyle(rowNum, 0, s); |
| } |
| if (styleCashe.ContainsKey(s)) { |
| ws.SetStyle(rowNum, 0, styleCashe[s]); |
| } else { |
| ExcelXfs st = CellXfs[s]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(s, newId); |
| ws._styles.SetValue(rowNum, 0, newId); |
| ws.SetStyle(rowNum, 0, newId); |
| } |
| } |
| |
| //Update individual cells |
| var cse2 = new CellsStoreEnumerator<int>( |
| ws._styles, |
| address._fromRow, |
| address._fromCol, |
| address._toRow, |
| address._toCol); |
| while (cse2.Next()) { |
| var s = cse2.Value; |
| if (styleCashe.ContainsKey(s)) { |
| ws.SetStyle(cse2.Row, cse2.Column, styleCashe[s]); |
| } else { |
| ExcelXfs st = CellXfs[s]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(s, newId); |
| cse2.Value = newId; |
| } |
| } |
| |
| //Update cells with styled rows |
| cse2 = new(ws._styles, 0, 1, 0, address._toCol); |
| while (cse2.Next()) { |
| for (int r = address._fromRow; r <= address._toRow; r++) { |
| if (!ws._styles.Exists(r, cse2.Column)) { |
| var s = cse2.Value; |
| if (styleCashe.ContainsKey(s)) { |
| ws.SetStyle(r, cse2.Column, styleCashe[s]); |
| } else { |
| ExcelXfs st = CellXfs[s]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(s, newId); |
| ws.SetStyle(r, cse2.Column, newId); |
| } |
| } |
| } |
| } |
| } else //Cellrange |
| { |
| for (int col = address.Start.Column; col <= address.End.Column; col++) { |
| for (int row = address.Start.Row; row <= address.End.Row; row++) { |
| var s = GetStyleId(ws, row, col); |
| if (styleCashe.ContainsKey(s)) { |
| ws.SetStyle(row, col, styleCashe[s]); |
| } else { |
| ExcelXfs st = CellXfs[s]; |
| int newId = st.GetNewId(CellXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| styleCashe.Add(s, newId); |
| ws.SetStyle(row, col, newId); |
| } |
| } |
| } |
| } |
| } |
| |
| internal int GetStyleId(ExcelWorksheet ws, int row, int col) { |
| int v = 0; |
| if (ws._styles.Exists(row, col, ref v)) { |
| return v; |
| } |
| if (ws._styles.Exists( |
| row, |
| 0, |
| ref v)) //First Row |
| { |
| return v; |
| } // then column |
| if (ws._styles.Exists(0, col, ref v)) { |
| return v; |
| } |
| int r = 0, |
| c = col; |
| if (ws._values.PrevCell(ref r, ref c)) { |
| var column = ws._values.GetValue(0, c) as ExcelColumn; |
| if (column != null |
| && column.ColumnMax |
| >= col) //Fixes issue 15174 |
| { |
| return ws._styles.GetValue(0, c); |
| } |
| return 0; |
| } |
| return 0; |
| } |
| |
| /// <summary> |
| /// Handles property changes on Named styles. |
| /// </summary> |
| /// <param name="sender"></param> |
| /// <param name="e"></param> |
| /// <returns></returns> |
| internal int NamedStylePropertyChange(StyleBase sender, StyleChangeEventArgs e) { |
| int index = NamedStyles.FindIndexById(e.Address); |
| if (index >= 0) { |
| int newId = CellStyleXfs[NamedStyles[index].StyleXfId] |
| .GetNewId(CellStyleXfs, sender, e.StyleClass, e.StyleProperty, e.Value); |
| int prevIx = NamedStyles[index].StyleXfId; |
| NamedStyles[index].StyleXfId = newId; |
| NamedStyles[index].Style.Index = newId; |
| |
| NamedStyles[index].XfId = int.MinValue; |
| foreach (var style in CellXfs) { |
| if (style.XfId == prevIx) { |
| style.XfId = newId; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| public ExcelStyleCollection<ExcelNumberFormatXml> NumberFormats = new(); |
| public ExcelStyleCollection<ExcelFontXml> Fonts = new(); |
| public ExcelStyleCollection<ExcelFillXml> Fills = new(); |
| public ExcelStyleCollection<ExcelBorderXml> Borders = new(); |
| public ExcelStyleCollection<ExcelXfs> CellStyleXfs = new(); |
| public ExcelStyleCollection<ExcelXfs> CellXfs = new(); |
| public ExcelStyleCollection<ExcelNamedStyleXml> NamedStyles = new(); |
| public ExcelStyleCollection<ExcelDxfStyleConditionalFormatting> Dxfs = new(); |
| |
| internal string Id => ""; |
| |
| public ExcelNamedStyleXml CreateNamedStyle(string name) { |
| return CreateNamedStyle(name, null); |
| } |
| |
| public ExcelNamedStyleXml CreateNamedStyle(string name, ExcelStyle template) { |
| if (_wb.Styles.NamedStyles.ExistsKey(name)) { |
| throw new(string.Format("Key {0} already exists in collection", name)); |
| } |
| |
| ExcelNamedStyleXml style; |
| style = new(NameSpaceManager, this); |
| int xfIdCopy, |
| positionId; |
| ExcelStyles styles; |
| if (template == null) { |
| // style.Style = new ExcelStyle(this, NamedStylePropertyChange, -1, name, 0); |
| xfIdCopy = 0; |
| positionId = -1; |
| styles = this; |
| } else { |
| if (template.PositionID < 0 && template.Styles == this) { |
| xfIdCopy = template.Index; |
| positionId = template.PositionID; |
| styles = this; |
| //style.Style = new ExcelStyle(this, NamedStylePropertyChange, Template.PositionID, name, Template.Index); |
| //style.StyleXfId = Template.Index; |
| } else { |
| xfIdCopy = template.XfId; |
| positionId = -1; |
| styles = template.Styles; |
| } |
| } |
| //Clone namedstyle |
| int styleXfId = CloneStyle(styles, xfIdCopy, true); |
| //Close cells style |
| CellStyleXfs[styleXfId].XfId = CellStyleXfs.Count - 1; |
| int xfid = CloneStyle(styles, xfIdCopy, false, true); //Always add a new style (We create a new named style here) |
| CellXfs[xfid].XfId = styleXfId; |
| style.Style = new(this, NamedStylePropertyChange, positionId, name, styleXfId); |
| style.StyleXfId = styleXfId; |
| |
| style.Name = name; |
| int ix = _wb.Styles.NamedStyles.Add(style.Name, style); |
| style.Style.SetIndex(ix); |
| //style.Style.XfId = ix; |
| return style; |
| } |
| |
| public void UpdateXml() { |
| RemoveUnusedStyles(); |
| |
| //NumberFormat |
| XmlNode nfNode = _styleXml.SelectSingleNode(_numberFormatsPath, _nameSpaceManager); |
| if (nfNode == null) { |
| CreateNode(_numberFormatsPath, true); |
| nfNode = _styleXml.SelectSingleNode(_numberFormatsPath, _nameSpaceManager); |
| } else { |
| nfNode.RemoveAll(); |
| } |
| |
| int count = 0; |
| int normalIx = NamedStyles.FindIndexById("Normal"); |
| if (NamedStyles.Count > 0 |
| && normalIx >= 0 |
| && NamedStyles[normalIx].Style.Numberformat.NumFmtID >= 164) { |
| ExcelNumberFormatXml nf = NumberFormats[NumberFormats.FindIndexById( |
| NamedStyles[normalIx].Style.Numberformat.Id)]; |
| nfNode.AppendChild( |
| nf.CreateXmlNode(_styleXml.CreateElement("numFmt", ExcelPackage._schemaMain))); |
| nf.newID = count++; |
| } |
| foreach (ExcelNumberFormatXml nf in NumberFormats) { |
| if (!nf.BuildIn /*&& nf.newID<0*/) //Buildin formats are not updated. |
| { |
| nfNode.AppendChild( |
| nf.CreateXmlNode(_styleXml.CreateElement("numFmt", ExcelPackage._schemaMain))); |
| nf.newID = count; |
| count++; |
| } |
| } |
| (nfNode as XmlElement).SetAttribute("count", count.ToString()); |
| |
| //Font |
| count = 0; |
| XmlNode fntNode = _styleXml.SelectSingleNode(_fontsPath, _nameSpaceManager); |
| fntNode.RemoveAll(); |
| |
| //Normal should be first in the collection |
| if (NamedStyles.Count > 0 && normalIx >= 0 && NamedStyles[normalIx].Style.Font.Index > 0) { |
| ExcelFontXml fnt = Fonts[NamedStyles[normalIx].Style.Font.Index]; |
| fntNode.AppendChild( |
| fnt.CreateXmlNode(_styleXml.CreateElement("font", ExcelPackage._schemaMain))); |
| fnt.newID = count++; |
| } |
| |
| foreach (ExcelFontXml fnt in Fonts) { |
| if (fnt.useCnt |
| > 0 /* && fnt.newID<0*/) { |
| fntNode.AppendChild( |
| fnt.CreateXmlNode(_styleXml.CreateElement("font", ExcelPackage._schemaMain))); |
| fnt.newID = count; |
| count++; |
| } |
| } |
| (fntNode as XmlElement).SetAttribute("count", count.ToString()); |
| |
| //Fills |
| count = 0; |
| XmlNode fillsNode = _styleXml.SelectSingleNode(_fillsPath, _nameSpaceManager); |
| fillsNode.RemoveAll(); |
| Fills[0].useCnt = 1; //Must exist (none); |
| Fills[1].useCnt = 1; //Must exist (gray125); |
| foreach (ExcelFillXml fill in Fills) { |
| if (fill.useCnt > 0) { |
| fillsNode.AppendChild( |
| fill.CreateXmlNode(_styleXml.CreateElement("fill", ExcelPackage._schemaMain))); |
| fill.newID = count; |
| count++; |
| } |
| } |
| |
| (fillsNode as XmlElement).SetAttribute("count", count.ToString()); |
| |
| //Borders |
| count = 0; |
| XmlNode bordersNode = _styleXml.SelectSingleNode(_bordersPath, _nameSpaceManager); |
| bordersNode.RemoveAll(); |
| Borders[0].useCnt = 1; //Must exist blank; |
| foreach (ExcelBorderXml border in Borders) { |
| if (border.useCnt > 0) { |
| bordersNode.AppendChild( |
| border.CreateXmlNode(_styleXml.CreateElement("border", ExcelPackage._schemaMain))); |
| border.newID = count; |
| count++; |
| } |
| } |
| (bordersNode as XmlElement).SetAttribute("count", count.ToString()); |
| |
| XmlNode styleXfsNode = _styleXml.SelectSingleNode(_cellStyleXfsPath, _nameSpaceManager); |
| if (styleXfsNode == null && NamedStyles.Count > 0) { |
| CreateNode(_cellStyleXfsPath); |
| styleXfsNode = _styleXml.SelectSingleNode(_cellStyleXfsPath, _nameSpaceManager); |
| } |
| if (NamedStyles.Count > 0) { |
| styleXfsNode.RemoveAll(); |
| } |
| //NamedStyles |
| count = normalIx > -1 ? 1 : 0; //If we have a normal style, we make sure it's added first. |
| |
| XmlNode cellStyleNode = _styleXml.SelectSingleNode(_cellStylesPath, _nameSpaceManager); |
| if (cellStyleNode != null) { |
| cellStyleNode.RemoveAll(); |
| } |
| XmlNode cellXfsNode = _styleXml.SelectSingleNode(_cellXfsPath, _nameSpaceManager); |
| cellXfsNode.RemoveAll(); |
| |
| if (NamedStyles.Count > 0 && normalIx >= 0) { |
| NamedStyles[normalIx].newID = 0; |
| AddNamedStyle(0, styleXfsNode, cellXfsNode, NamedStyles[normalIx]); |
| } |
| foreach (ExcelNamedStyleXml style in NamedStyles) { |
| if (!style.Name.Equals("normal", StringComparison.InvariantCultureIgnoreCase)) { |
| AddNamedStyle(count++, styleXfsNode, cellXfsNode, style); |
| } else { |
| style.newID = 0; |
| } |
| cellStyleNode.AppendChild( |
| style.CreateXmlNode(_styleXml.CreateElement("cellStyle", ExcelPackage._schemaMain))); |
| } |
| if (cellStyleNode != null) { |
| (cellStyleNode as XmlElement).SetAttribute("count", count.ToString()); |
| } |
| if (styleXfsNode != null) { |
| (styleXfsNode as XmlElement).SetAttribute("count", count.ToString()); |
| } |
| |
| //CellStyle |
| int xfix = 0; |
| foreach (ExcelXfs xf in CellXfs) { |
| if (xf.useCnt > 0 && !(normalIx >= 0 && NamedStyles[normalIx].XfId == xfix)) { |
| cellXfsNode.AppendChild( |
| xf.CreateXmlNode(_styleXml.CreateElement("xf", ExcelPackage._schemaMain))); |
| xf.newID = count; |
| count++; |
| } |
| xfix++; |
| } |
| (cellXfsNode as XmlElement).SetAttribute("count", count.ToString()); |
| |
| //Set dxf styling for conditional Formatting |
| XmlNode dxfsNode = _styleXml.SelectSingleNode(_dxfsPath, _nameSpaceManager); |
| foreach (var ws in _wb.Worksheets) { |
| if (ws is ExcelChartsheet) { |
| continue; |
| } |
| foreach (var cf in ws.ConditionalFormatting) { |
| if (cf.Style.HasValue) { |
| int ix = Dxfs.FindIndexById(cf.Style.Id); |
| if (ix < 0) { |
| ((ExcelConditionalFormattingRule)cf).DxfId = Dxfs.Count; |
| Dxfs.Add(cf.Style.Id, cf.Style); |
| var elem = ((XmlDocument)TopNode).CreateElement("d", "dxf", ExcelPackage._schemaMain); |
| cf.Style.CreateNodes(new XmlHelperInstance(NameSpaceManager, elem), ""); |
| dxfsNode.AppendChild(elem); |
| } else { |
| ((ExcelConditionalFormattingRule)cf).DxfId = ix; |
| } |
| } |
| } |
| } |
| if (dxfsNode != null) { |
| (dxfsNode as XmlElement).SetAttribute("count", Dxfs.Count.ToString()); |
| } |
| } |
| |
| private void AddNamedStyle( |
| int id, |
| XmlNode styleXfsNode, |
| XmlNode cellXfsNode, |
| ExcelNamedStyleXml style) { |
| var styleXfs = CellStyleXfs[style.StyleXfId]; |
| styleXfsNode.AppendChild( |
| styleXfs.CreateXmlNode(_styleXml.CreateElement("xf", ExcelPackage._schemaMain), true)); |
| styleXfs.newID = id; |
| styleXfs.XfId = style.StyleXfId; |
| |
| var ix = CellXfs.FindIndexById(styleXfs.Id); |
| if (ix < 0) { |
| cellXfsNode.AppendChild( |
| styleXfs.CreateXmlNode(_styleXml.CreateElement("xf", ExcelPackage._schemaMain))); |
| } else { |
| if (id < 0) { |
| CellXfs[ix].XfId = id; |
| } |
| cellXfsNode.AppendChild( |
| CellXfs[ix].CreateXmlNode(_styleXml.CreateElement("xf", ExcelPackage._schemaMain))); |
| CellXfs[ix].useCnt = 0; |
| CellXfs[ix].newID = id; |
| } |
| |
| if (style.XfId >= 0) { |
| style.XfId = CellXfs[style.XfId].newID; |
| } else { |
| style.XfId = 0; |
| } |
| } |
| |
| private void RemoveUnusedStyles() { |
| CellXfs[0].useCnt = 1; //First item is allways used. |
| foreach (ExcelWorksheet sheet in _wb.Worksheets) { |
| var cse = new CellsStoreEnumerator<int>(sheet._styles); |
| while (cse.Next()) { |
| var v = cse.Value; |
| if (v >= 0) { |
| CellXfs[v].useCnt++; |
| } |
| } |
| } |
| foreach (ExcelNamedStyleXml ns in NamedStyles) { |
| CellStyleXfs[ns.StyleXfId].useCnt++; |
| } |
| |
| foreach (ExcelXfs xf in CellXfs) { |
| if (xf.useCnt > 0) { |
| if (xf.FontId >= 0) { |
| Fonts[xf.FontId].useCnt++; |
| } |
| if (xf.FillId >= 0) { |
| Fills[xf.FillId].useCnt++; |
| } |
| if (xf.BorderId >= 0) { |
| Borders[xf.BorderId].useCnt++; |
| } |
| } |
| } |
| foreach (ExcelXfs xf in CellStyleXfs) { |
| if (xf.useCnt > 0) { |
| if (xf.FontId >= 0) { |
| Fonts[xf.FontId].useCnt++; |
| } |
| if (xf.FillId >= 0) { |
| Fills[xf.FillId].useCnt++; |
| } |
| if (xf.BorderId >= 0) { |
| Borders[xf.BorderId].useCnt++; |
| } |
| } |
| } |
| } |
| |
| internal int GetStyleIdFromName(string name) { |
| int i = NamedStyles.FindIndexById(name); |
| if (i >= 0) { |
| int id = NamedStyles[i].XfId; |
| if (id < 0) { |
| int styleXfId = NamedStyles[i].StyleXfId; |
| ExcelXfs newStyle = CellStyleXfs[styleXfId].Copy(); |
| newStyle.XfId = styleXfId; |
| id = CellXfs.FindIndexById(newStyle.Id); |
| if (id < 0) { |
| id = CellXfs.Add(newStyle.Id, newStyle); |
| } |
| NamedStyles[i].XfId = id; |
| } |
| return id; |
| } |
| return 0; |
| //throw(new Exception("Named style does not exist")); |
| } |
| |
| private string GetXmlNode(XmlNode node) { |
| if (node == null) { |
| return ""; |
| } |
| if (node.Value != null) { |
| return node.Value; |
| } |
| return ""; |
| } |
| |
| internal int CloneStyle(ExcelStyles style, int styleId) { |
| return CloneStyle(style, styleId, false, false); |
| } |
| |
| internal int CloneStyle(ExcelStyles style, int styleId, bool isNamedStyle) { |
| return CloneStyle(style, styleId, isNamedStyle, false); |
| } |
| |
| internal int CloneStyle(ExcelStyles style, int styleId, bool isNamedStyle, bool allwaysAdd) { |
| var xfs = isNamedStyle ? style.CellStyleXfs[styleId] : style.CellXfs[styleId]; |
| ExcelXfs newXfs = xfs.Copy(this); |
| //Numberformat |
| if (xfs.NumberFormatId > 0) { |
| //rake36: Two problems here... |
| //rake36: 1. the first time through when format stays equal to String.Empty, it adds a string.empty to the list of Number Formats |
| //rake36: 2. when adding a second sheet, if the numberformatid == 164, it finds the 164 added by previous sheets but was using the array index |
| //rake36: for the numberformatid |
| |
| string format = string.Empty; |
| foreach (var fmt in style.NumberFormats) { |
| if (fmt.NumFmtId == xfs.NumberFormatId) { |
| format = fmt.Format; |
| break; |
| } |
| } |
| //rake36: Don't add another format if it's blank |
| if (!String.IsNullOrEmpty(format)) { |
| int ix = NumberFormats.FindIndexById(format); |
| if (ix < 0) { |
| var item = new ExcelNumberFormatXml(NameSpaceManager) { |
| Format = format, |
| NumFmtId = NumberFormats.NextId++, |
| }; |
| NumberFormats.Add(format, item); |
| //rake36: Use the just added format id |
| newXfs.NumberFormatId = item.NumFmtId; |
| } else { |
| //rake36: Use the format id defined by the index... not the index itself |
| newXfs.NumberFormatId = NumberFormats[ix].NumFmtId; |
| } |
| } |
| } |
| |
| //Font |
| if (xfs.FontId > -1) { |
| int ix = Fonts.FindIndexById(xfs.Font.Id); |
| if (ix < 0) { |
| ExcelFontXml item = style.Fonts[xfs.FontId].Copy(); |
| ix = Fonts.Add(xfs.Font.Id, item); |
| } |
| newXfs.FontId = ix; |
| } |
| |
| //Border |
| if (xfs.BorderId > -1) { |
| int ix = Borders.FindIndexById(xfs.Border.Id); |
| if (ix < 0) { |
| ExcelBorderXml item = style.Borders[xfs.BorderId].Copy(); |
| ix = Borders.Add(xfs.Border.Id, item); |
| } |
| newXfs.BorderId = ix; |
| } |
| |
| //Fill |
| if (xfs.FillId > -1) { |
| int ix = Fills.FindIndexById(xfs.Fill.Id); |
| if (ix < 0) { |
| var item = style.Fills[xfs.FillId].Copy(); |
| ix = Fills.Add(xfs.Fill.Id, item); |
| } |
| newXfs.FillId = ix; |
| } |
| |
| //Named style reference |
| if (xfs.XfId > 0) { |
| var id = style.CellStyleXfs[xfs.XfId].Id; |
| var newId = CellStyleXfs.FindIndexById(id); |
| if (newId >= 0) { |
| newXfs.XfId = newId; |
| } else if (style._wb != _wb |
| && allwaysAdd |
| == false) //Not the same workbook, copy the namedstyle to the workbook or match the id |
| { |
| var nsFind = style.NamedStyles.ToDictionary(d => (d.StyleXfId)); |
| if (nsFind.ContainsKey(xfs.XfId)) { |
| var st = nsFind[xfs.XfId]; |
| if (NamedStyles.ExistsKey(st.Name)) { |
| newXfs.XfId = NamedStyles.FindIndexById(st.Name); |
| } else { |
| var ns = CreateNamedStyle(st.Name, st.Style); |
| newXfs.XfId = NamedStyles.Count - 1; |
| } |
| } |
| } |
| } |
| |
| int index; |
| if (isNamedStyle) { |
| index = CellStyleXfs.Add(newXfs.Id, newXfs); |
| } else { |
| if (allwaysAdd) { |
| index = CellXfs.Add(newXfs.Id, newXfs); |
| } else { |
| index = CellXfs.FindIndexById(newXfs.Id); |
| if (index < 0) { |
| index = CellXfs.Add(newXfs.Id, newXfs); |
| } |
| } |
| } |
| return index; |
| } |
| } |