| /******************************************************************************* | 
 |  * 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; | 
 |     } | 
 |   } | 
 | } |