| /******************************************************************************* | |
| * 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 Added 21-MAR-2011 | |
| * Jan Källman License changed GPL-->LGPL 2011-12-16 | |
| *******************************************************************************/ | |
| using System; | |
| using System.Globalization; | |
| using System.Xml; | |
| namespace AppsheetEpplus; | |
| /// <summary> | |
| /// Defines the axis for a PivotTable | |
| /// </summary> | |
| public enum ePivotFieldAxis { | |
| /// <summary> | |
| /// None | |
| /// </summary> | |
| None = -1, | |
| /// <summary> | |
| /// Column axis | |
| /// </summary> | |
| Column, | |
| /// <summary> | |
| /// Page axis (Include Count Filter) | |
| /// | |
| /// </summary> | |
| Page, | |
| /// <summary> | |
| /// Row axis | |
| /// </summary> | |
| Row, | |
| /// <summary> | |
| /// Values axis | |
| /// </summary> | |
| Values, | |
| } | |
| /// <summary> | |
| /// Build-in table row functions | |
| /// </summary> | |
| public enum DataFieldFunctions { | |
| Average, | |
| Count, | |
| CountNums, | |
| Max, | |
| Min, | |
| Product, | |
| None, | |
| StdDev, | |
| StdDevP, | |
| Sum, | |
| Var, | |
| VarP, | |
| } | |
| /// <summary> | |
| /// Defines the data formats for a field in the PivotTable | |
| /// </summary> | |
| public enum eShowDataAs { | |
| /// <summary> | |
| /// Indicates the field is shown as the "difference from" a value. | |
| /// </summary> | |
| Difference, | |
| /// <summary> | |
| /// Indicates the field is shown as the "index. | |
| /// </summary> | |
| Index, | |
| /// <summary> | |
| /// Indicates that the field is shown as its normal datatype. | |
| /// </summary> | |
| Normal, | |
| /// <summary> | |
| /// /Indicates the field is show as the "percentage of" a value | |
| /// </summary> | |
| Percent, | |
| /// <summary> | |
| /// Indicates the field is shown as the "percentage difference from" a value. | |
| /// </summary> | |
| PercentDiff, | |
| /// <summary> | |
| /// Indicates the field is shown as the percentage of column. | |
| /// </summary> | |
| PercentOfCol, | |
| /// <summary> | |
| /// Indicates the field is shown as the percentage of row | |
| /// </summary> | |
| PercentOfRow, | |
| /// <summary> | |
| /// Indicates the field is shown as percentage of total. | |
| /// </summary> | |
| PercentOfTotal, | |
| /// <summary> | |
| /// Indicates the field is shown as running total in the table. | |
| /// </summary> | |
| RunTotal, | |
| } | |
| /// <summary> | |
| /// Built-in subtotal functions | |
| /// </summary> | |
| [Flags] | |
| public enum eSubTotalFunctions { | |
| None = 1, | |
| Count = 2, | |
| CountA = 4, | |
| Avg = 8, | |
| Default = 16, | |
| Min = 32, | |
| Max = 64, | |
| Product = 128, | |
| StdDev = 256, | |
| StdDevP = 512, | |
| Sum = 1024, | |
| Var = 2048, | |
| VarP = 4096, | |
| } | |
| /// <summary> | |
| /// Data grouping | |
| /// </summary> | |
| [Flags] | |
| public enum eDateGroupBy { | |
| Years = 1, | |
| Quarters = 2, | |
| Months = 4, | |
| Days = 8, | |
| Hours = 16, | |
| Minutes = 32, | |
| Seconds = 64, | |
| } | |
| /// <summary> | |
| /// Sorting | |
| /// </summary> | |
| public enum eSortType { | |
| None, | |
| Ascending, | |
| Descending, | |
| } | |
| /// <summary> | |
| /// A pivot table field. | |
| /// </summary> | |
| public class ExcelPivotTableField : XmlHelper { | |
| internal ExcelPivotTable _table; | |
| internal ExcelPivotTableField( | |
| XmlNamespaceManager ns, | |
| XmlNode topNode, | |
| ExcelPivotTable table, | |
| int index, | |
| int baseIndex) | |
| : base(ns, topNode) { | |
| Index = index; | |
| BaseIndex = baseIndex; | |
| _table = table; | |
| } | |
| public int Index { get; set; } | |
| internal int BaseIndex { get; set; } | |
| /// <summary> | |
| /// Name of the field | |
| /// </summary> | |
| public string Name { | |
| get { | |
| string v = GetXmlNodeString("@name"); | |
| if (v == "") { | |
| return _cacheFieldHelper.GetXmlNodeString("@name"); | |
| } | |
| return v; | |
| } | |
| set => SetXmlNodeString("@name", value); | |
| } | |
| /// <summary> | |
| /// Compact mode | |
| /// </summary> | |
| public bool Compact { | |
| get => GetXmlNodeBool("@compact"); | |
| set => SetXmlNodeBool("@compact", value); | |
| } | |
| /// <summary> | |
| /// A boolean that indicates whether the items in this field should be shown in Outline form | |
| /// </summary> | |
| public bool Outline { | |
| get => GetXmlNodeBool("@outline"); | |
| set => SetXmlNodeBool("@outline", value); | |
| } | |
| /// <summary> | |
| /// The custom text that is displayed for the subtotals label | |
| /// </summary> | |
| public bool SubtotalTop { | |
| get => GetXmlNodeBool("@subtotalTop"); | |
| set => SetXmlNodeBool("@subtotalTop", value); | |
| } | |
| /// <summary> | |
| /// A boolean that indicates whether to show all items for this field | |
| /// </summary> | |
| public bool ShowAll { | |
| get => GetXmlNodeBool("@showAll"); | |
| set => SetXmlNodeBool("@showAll", value); | |
| } | |
| /// <summary> | |
| /// The type of sort that is applied to this field | |
| /// </summary> | |
| public eSortType Sort { | |
| get { | |
| string v = GetXmlNodeString("@sortType"); | |
| return v == "" ? eSortType.None : (eSortType)Enum.Parse(typeof(eSortType), v, true); | |
| } | |
| set { | |
| if (value == eSortType.None) { | |
| DeleteNode("@sortType"); | |
| } else { | |
| SetXmlNodeString("@sortType", value.ToString().ToLower(CultureInfo.InvariantCulture)); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// A boolean that indicates whether manual filter is in inclusive mode | |
| /// </summary> | |
| public bool IncludeNewItemsInFilter { | |
| get => GetXmlNodeBool("@includeNewItemsInFilter"); | |
| set => SetXmlNodeBool("@includeNewItemsInFilter", value); | |
| } | |
| /// <summary> | |
| /// Enumeration of the different subtotal operations that can be applied to page, row or column fields | |
| /// </summary> | |
| public eSubTotalFunctions SubTotalFunctions { | |
| get { | |
| eSubTotalFunctions ret = 0; | |
| XmlNodeList nl = TopNode.SelectNodes("d:items/d:item/@t", NameSpaceManager); | |
| if (nl.Count == 0) { | |
| return eSubTotalFunctions.None; | |
| } | |
| foreach (XmlAttribute item in nl) { | |
| try { | |
| ret |= (eSubTotalFunctions)Enum.Parse(typeof(eSubTotalFunctions), item.Value, true); | |
| } catch (ArgumentException ex) { | |
| throw new ArgumentException( | |
| "Unable to parse value of " | |
| + item.Value | |
| + " to a valid pivot table subtotal function", | |
| ex); | |
| } | |
| } | |
| return ret; | |
| } | |
| set { | |
| if ((value & eSubTotalFunctions.None) == eSubTotalFunctions.None | |
| && (value != eSubTotalFunctions.None)) { | |
| throw (new ArgumentException("Value None can not be combined with other values.")); | |
| } | |
| if ((value & eSubTotalFunctions.Default) == eSubTotalFunctions.Default | |
| && (value != eSubTotalFunctions.Default)) { | |
| throw (new ArgumentException("Value Default can not be combined with other values.")); | |
| } | |
| // remove old attribute | |
| XmlNodeList nl = TopNode.SelectNodes("d:items/d:item/@t", NameSpaceManager); | |
| if (nl.Count > 0) { | |
| foreach (XmlAttribute item in nl) { | |
| DeleteNode("@" + item.Value + "Subtotal"); | |
| item.OwnerElement.ParentNode.RemoveChild(item.OwnerElement); | |
| } | |
| } | |
| if (value == eSubTotalFunctions.None) { | |
| // for no subtotals, set defaultSubtotal to off | |
| SetXmlNodeBool("@defaultSubtotal", false); | |
| TopNode.InnerXml = ""; | |
| } else { | |
| string innerXml = ""; | |
| int count = 0; | |
| foreach (eSubTotalFunctions e in Enum.GetValues(typeof(eSubTotalFunctions))) { | |
| if ((value & e) == e) { | |
| var newTotalType = e.ToString(); | |
| var totalType = | |
| char.ToLower(newTotalType[0], CultureInfo.InvariantCulture) | |
| + newTotalType.Substring(1); | |
| // add new attribute | |
| SetXmlNodeBool("@" + totalType + "Subtotal", true); | |
| innerXml += "<item t=\"" + totalType + "\" />"; | |
| count++; | |
| } | |
| } | |
| TopNode.InnerXml = string.Format("<items count=\"{0}\">{1}</items>", count, innerXml); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Type of axis | |
| /// </summary> | |
| public ePivotFieldAxis Axis { | |
| get { | |
| switch (GetXmlNodeString("@axis")) { | |
| case "axisRow": | |
| return ePivotFieldAxis.Row; | |
| case "axisCol": | |
| return ePivotFieldAxis.Column; | |
| case "axisPage": | |
| return ePivotFieldAxis.Page; | |
| case "axisValues": | |
| return ePivotFieldAxis.Values; | |
| default: | |
| return ePivotFieldAxis.None; | |
| } | |
| } | |
| internal set { | |
| switch (value) { | |
| case ePivotFieldAxis.Row: | |
| SetXmlNodeString("@axis", "axisRow"); | |
| break; | |
| case ePivotFieldAxis.Column: | |
| SetXmlNodeString("@axis", "axisCol"); | |
| break; | |
| case ePivotFieldAxis.Values: | |
| SetXmlNodeString("@axis", "axisValues"); | |
| break; | |
| case ePivotFieldAxis.Page: | |
| SetXmlNodeString("@axis", "axisPage"); | |
| break; | |
| default: | |
| DeleteNode("@axis"); | |
| break; | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// If the field is a row field | |
| /// </summary> | |
| public bool IsRowField { | |
| get => | |
| (TopNode.SelectSingleNode( | |
| string.Format("../../d:rowFields/d:field[@x={0}]", Index), | |
| NameSpaceManager) != null); | |
| internal set { | |
| if (value) { | |
| var rowsNode = TopNode.SelectSingleNode("../../d:rowFields", NameSpaceManager); | |
| if (rowsNode == null) { | |
| _table.CreateNode("d:rowFields"); | |
| } | |
| rowsNode = TopNode.SelectSingleNode("../../d:rowFields", NameSpaceManager); | |
| AppendField(rowsNode, Index, "field", "x"); | |
| if (BaseIndex == Index) { | |
| TopNode.InnerXml = "<items count=\"1\"><item t=\"default\" /></items>"; | |
| } else { | |
| TopNode.InnerXml = "<items count=\"0\"></items>"; | |
| } | |
| } else { | |
| XmlElement node = | |
| TopNode.SelectSingleNode( | |
| string.Format("../../d:rowFields/d:field[@x={0}]", Index), | |
| NameSpaceManager) as XmlElement; | |
| if (node != null) { | |
| node.ParentNode.RemoveChild(node); | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// If the field is a column field | |
| /// </summary> | |
| public bool IsColumnField { | |
| get => | |
| (TopNode.SelectSingleNode( | |
| string.Format("../../d:colFields/d:field[@x={0}]", Index), | |
| NameSpaceManager) != null); | |
| internal set { | |
| if (value) { | |
| var columnsNode = TopNode.SelectSingleNode("../../d:colFields", NameSpaceManager); | |
| if (columnsNode == null) { | |
| _table.CreateNode("d:colFields"); | |
| } | |
| columnsNode = TopNode.SelectSingleNode("../../d:colFields", NameSpaceManager); | |
| AppendField(columnsNode, Index, "field", "x"); | |
| if (BaseIndex == Index) { | |
| TopNode.InnerXml = "<items count=\"1\"><item t=\"default\" /></items>"; | |
| } else { | |
| TopNode.InnerXml = "<items count=\"0\"></items>"; | |
| } | |
| } else { | |
| XmlElement node = | |
| TopNode.SelectSingleNode( | |
| string.Format("../../d:colFields/d:field[@x={0}]", Index), | |
| NameSpaceManager) as XmlElement; | |
| if (node != null) { | |
| node.ParentNode.RemoveChild(node); | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// If the field is a datafield | |
| /// </summary> | |
| public bool IsDataField => GetXmlNodeBool("@dataField", false); | |
| /// <summary> | |
| /// If the field is a page field. | |
| /// </summary> | |
| public bool IsPageField { | |
| get => (Axis == ePivotFieldAxis.Page); | |
| internal set { | |
| if (value) { | |
| var dataFieldsNode = TopNode.SelectSingleNode("../../d:pageFields", NameSpaceManager); | |
| if (dataFieldsNode == null) { | |
| _table.CreateNode("d:pageFields"); | |
| dataFieldsNode = TopNode.SelectSingleNode("../../d:pageFields", NameSpaceManager); | |
| } | |
| TopNode.InnerXml = "<items count=\"1\"><item t=\"default\" /></items>"; | |
| XmlElement node = AppendField(dataFieldsNode, Index, "pageField", "fld"); | |
| _pageFieldSettings = new(NameSpaceManager, node, this, Index); | |
| } else { | |
| _pageFieldSettings = null; | |
| XmlElement node = | |
| TopNode.SelectSingleNode( | |
| string.Format("../../d:pageFields/d:pageField[@fld={0}]", Index), | |
| NameSpaceManager) as XmlElement; | |
| if (node != null) { | |
| node.ParentNode.RemoveChild(node); | |
| } | |
| } | |
| } | |
| } | |
| //public ExcelPivotGrouping DateGrouping | |
| //{ | |
| //} | |
| internal ExcelPivotTablePageFieldSettings _pageFieldSettings; | |
| public ExcelPivotTablePageFieldSettings PageFieldSettings => _pageFieldSettings; | |
| internal eDateGroupBy DateGrouping { get; set; } | |
| private ExcelPivotTableFieldGroup _grouping; | |
| /// <summary> | |
| /// Grouping settings. | |
| /// Null if the field has no grouping otherwise ExcelPivotTableFieldNumericGroup or ExcelPivotTableFieldNumericGroup. | |
| /// </summary> | |
| public ExcelPivotTableFieldGroup Grouping => _grouping; | |
| internal XmlElement AppendField( | |
| XmlNode rowsNode, | |
| int index, | |
| string fieldNodeText, | |
| string indexAttrText) { | |
| XmlElement prevField = null, | |
| newElement; | |
| foreach (XmlElement field in rowsNode.ChildNodes) { | |
| string x = field.GetAttribute(indexAttrText); | |
| if (int.TryParse(x, out var fieldIndex)) { | |
| if (fieldIndex | |
| == index) //Row already exists | |
| { | |
| return field; | |
| } | |
| //else if (fieldIndex > index) | |
| //{ | |
| // newElement = rowsNode.OwnerDocument.CreateElement(fieldNodeText, ExcelPackage.schemaMain); | |
| // newElement.SetAttribute(indexAttrText, index.ToString()); | |
| // rowsNode.InsertAfter(newElement, field); | |
| //} | |
| } | |
| prevField = field; | |
| } | |
| newElement = rowsNode.OwnerDocument.CreateElement(fieldNodeText, ExcelPackage._schemaMain); | |
| newElement.SetAttribute(indexAttrText, index.ToString()); | |
| rowsNode.InsertAfter(newElement, prevField); | |
| return newElement; | |
| } | |
| internal XmlHelperInstance _cacheFieldHelper; | |
| internal void SetCacheFieldNode(XmlNode cacheField) { | |
| _cacheFieldHelper = new(NameSpaceManager, cacheField); | |
| var groupNode = cacheField.SelectSingleNode("d:fieldGroup", NameSpaceManager); | |
| if (groupNode != null) { | |
| var groupBy = groupNode.SelectSingleNode("d:rangePr/@groupBy", NameSpaceManager); | |
| if (groupBy == null) { | |
| _grouping = new ExcelPivotTableFieldNumericGroup(NameSpaceManager, cacheField); | |
| } else { | |
| DateGrouping = (eDateGroupBy)Enum.Parse(typeof(eDateGroupBy), groupBy.Value, true); | |
| _grouping = new ExcelPivotTableFieldDateGroup(NameSpaceManager, groupNode); | |
| } | |
| } | |
| } | |
| internal ExcelPivotTableFieldCollectionBase<ExcelPivotTableFieldItem> _items; | |
| /// <summary> | |
| /// Pivottable field Items. Used for grouping. | |
| /// </summary> | |
| public ExcelPivotTableFieldCollectionBase<ExcelPivotTableFieldItem> Items { | |
| get { | |
| if (_items == null) { | |
| _items = new(_table); | |
| foreach (XmlNode node in TopNode.SelectNodes("d:items//d:item", NameSpaceManager)) { | |
| var item = new ExcelPivotTableFieldItem(NameSpaceManager, node, this); | |
| if (item.T == "") { | |
| _items.AddInternal(item); | |
| } | |
| } | |
| } | |
| return _items; | |
| } | |
| } | |
| } |