| /******************************************************************************* | 
 |  * 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.Collections.Generic; | 
 | using System.Text; | 
 | using System.Xml; | 
 | using System.Globalization; | 
 |  | 
 | namespace OfficeOpenXml.Table.PivotTable | 
 | { | 
 |      | 
 |     /// <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"); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     return v; | 
 |                 } | 
 |             } | 
 |             set | 
 |             { | 
 |                 SetXmlNodeString("@name", value); | 
 |             } | 
 |         } | 
 |         /// <summary> | 
 |         /// Compact mode | 
 |         /// </summary> | 
 |         public bool Compact | 
 |         {  | 
 |             get | 
 |             { | 
 |                 return 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 | 
 |             { | 
 |                 return GetXmlNodeBool("@outline"); | 
 |             } | 
 |             set | 
 |             { | 
 |                 SetXmlNodeBool("@outline",value); | 
 |             } | 
 |         } | 
 |         /// <summary> | 
 |         /// The custom text that is displayed for the subtotals label | 
 |         /// </summary> | 
 |         public bool SubtotalTop  | 
 |         {  | 
 |             get | 
 |             { | 
 |                 return GetXmlNodeBool("@subtotalTop"); | 
 |             } | 
 |             set | 
 |             { | 
 |                 SetXmlNodeBool("@subtotalTop",value); | 
 |             } | 
 |         } | 
 |         /// <summary> | 
 |         /// A boolean that indicates whether to show all items for this field | 
 |         /// </summary> | 
 |         public bool ShowAll  | 
 |         {  | 
 |             get | 
 |             { | 
 |                 return 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 | 
 |             { | 
 |                 return 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 | 
 |             { | 
 |                 return (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 | 
 |             { | 
 |                 return (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 | 
 |         { | 
 |             get | 
 |             { | 
 |                 return GetXmlNodeBool("@dataField", false); | 
 |             } | 
 |         } | 
 |         /// <summary> | 
 |         /// If the field is a page field. | 
 |         /// </summary> | 
 |         public bool IsPageField | 
 |         { | 
 |             get | 
 |             { | 
 |                 return (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 ExcelPivotTablePageFieldSettings(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 = null; | 
 |         public ExcelPivotTablePageFieldSettings PageFieldSettings | 
 |         { | 
 |             get | 
 |             { | 
 |                 return _pageFieldSettings; | 
 |             } | 
 |         } | 
 |         internal eDateGroupBy DateGrouping | 
 |         { | 
 |             get; | 
 |             set; | 
 |         } | 
 |         ExcelPivotTableFieldGroup _grouping=null; | 
 |         /// <summary> | 
 |         /// Grouping settings.  | 
 |         /// Null if the field has no grouping otherwise ExcelPivotTableFieldNumericGroup or ExcelPivotTableFieldNumericGroup. | 
 |         /// </summary>         | 
 |         public ExcelPivotTableFieldGroup Grouping | 
 |         { | 
 |             get | 
 |             { | 
 |                 return _grouping; | 
 |             } | 
 |         } | 
 |         #region Private & internal Methods | 
 |         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); | 
 |                 int fieldIndex; | 
 |                 if(int.TryParse(x, out 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 = null; | 
 |         internal void SetCacheFieldNode(XmlNode cacheField) | 
 |         { | 
 |             _cacheFieldHelper = new XmlHelperInstance(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); | 
 |                 } | 
 |             } | 
 |         } | 
 |         #endregion | 
 |         #region Grouping | 
 |         internal ExcelPivotTableFieldDateGroup SetDateGroup(eDateGroupBy GroupBy, DateTime StartDate, DateTime EndDate, int interval) | 
 |         { | 
 |             ExcelPivotTableFieldDateGroup group; | 
 |             group = new ExcelPivotTableFieldDateGroup(NameSpaceManager, _cacheFieldHelper.TopNode); | 
 |             _cacheFieldHelper.SetXmlNodeBool("d:sharedItems/@containsDate", true); | 
 |             _cacheFieldHelper.SetXmlNodeBool("d:sharedItems/@containsNonDate", false); | 
 |             _cacheFieldHelper.SetXmlNodeBool("d:sharedItems/@containsSemiMixedTypes", false); | 
 |  | 
 |             group.TopNode.InnerXml += string.Format("<fieldGroup base=\"{0}\"><rangePr groupBy=\"{1}\" /><groupItems /></fieldGroup>", BaseIndex, GroupBy.ToString().ToLower(CultureInfo.InvariantCulture)); | 
 |  | 
 |             if (StartDate.Year < 1900) | 
 |             { | 
 |                 _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/d:rangePr/@startDate", "1900-01-01T00:00:00"); | 
 |             } | 
 |             else | 
 |             { | 
 |                 _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/d:rangePr/@startDate", StartDate.ToString("s", CultureInfo.InvariantCulture)); | 
 |                 _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/d:rangePr/@autoStart", "0"); | 
 |             } | 
 |  | 
 |             if (EndDate==DateTime.MaxValue) | 
 |             { | 
 |                 _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/d:rangePr/@endDate", "9999-12-31T00:00:00"); | 
 |             } | 
 |             else | 
 |             { | 
 |                 _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/d:rangePr/@endDate", EndDate.ToString("s", CultureInfo.InvariantCulture)); | 
 |                 _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/d:rangePr/@autoEnd", "0"); | 
 |             } | 
 |  | 
 |             int items = AddDateGroupItems(group, GroupBy, StartDate, EndDate, interval); | 
 |             AddFieldItems(items); | 
 |  | 
 |             _grouping = group; | 
 |             return group; | 
 |         } | 
 |         internal ExcelPivotTableFieldNumericGroup SetNumericGroup(double start, double end, double interval) | 
 |         { | 
 |             ExcelPivotTableFieldNumericGroup group; | 
 |             group = new ExcelPivotTableFieldNumericGroup(NameSpaceManager, _cacheFieldHelper.TopNode); | 
 |             _cacheFieldHelper.SetXmlNodeBool("d:sharedItems/@containsNumber", true); | 
 |             _cacheFieldHelper.SetXmlNodeBool("d:sharedItems/@containsInteger", true); | 
 |             _cacheFieldHelper.SetXmlNodeBool("d:sharedItems/@containsSemiMixedTypes", false); | 
 |             _cacheFieldHelper.SetXmlNodeBool("d:sharedItems/@containsString", false); | 
 |  | 
 |             group.TopNode.InnerXml += string.Format("<fieldGroup base=\"{0}\"><rangePr autoStart=\"0\" autoEnd=\"0\" startNum=\"{1}\" endNum=\"{2}\" groupInterval=\"{3}\"/><groupItems /></fieldGroup>", BaseIndex, start.ToString(CultureInfo.InvariantCulture), end.ToString(CultureInfo.InvariantCulture), interval.ToString(CultureInfo.InvariantCulture)); | 
 |             int items = AddNumericGroupItems(group, start, end, interval); | 
 |             AddFieldItems(items); | 
 |  | 
 |             _grouping = group; | 
 |             return group; | 
 |         } | 
 |  | 
 |         private int AddNumericGroupItems(ExcelPivotTableFieldNumericGroup group, double start, double end, double interval) | 
 |         { | 
 |             if (interval < 0) | 
 |             { | 
 |                 throw (new Exception("The interval must be a positiv")); | 
 |             } | 
 |             if (start > end) | 
 |             { | 
 |                 throw(new Exception("Then End number must be larger than the Start number")); | 
 |             } | 
 |  | 
 |             XmlElement groupItems = group.TopNode.SelectSingleNode("d:fieldGroup/d:groupItems", group.NameSpaceManager) as XmlElement; | 
 |             int items = 2; | 
 |             //First date | 
 |             double index=start; | 
 |             double nextIndex=start+interval; | 
 |             AddGroupItem(groupItems, "<" + start.ToString(CultureInfo.InvariantCulture)); | 
 |  | 
 |             while (index < end) | 
 |             { | 
 |                 AddGroupItem(groupItems, string.Format("{0}-{1}", index.ToString(CultureInfo.InvariantCulture), nextIndex.ToString(CultureInfo.InvariantCulture))); | 
 |                 index=nextIndex; | 
 |                 nextIndex+=interval; | 
 |                 items++; | 
 |             } | 
 |             AddGroupItem(groupItems, ">" + nextIndex.ToString(CultureInfo.InvariantCulture)); | 
 |             return items; | 
 |         } | 
 |  | 
 |         private void AddFieldItems(int items) | 
 |         { | 
 |             XmlElement prevNode = null; | 
 |             XmlElement itemsNode = TopNode.SelectSingleNode("d:items", NameSpaceManager) as XmlElement; | 
 |             for (int x = 0; x < items; x++) | 
 |             { | 
 |                 var itemNode = itemsNode.OwnerDocument.CreateElement("item", ExcelPackage.schemaMain); | 
 |                 itemNode.SetAttribute("x", x.ToString()); | 
 |                 if (prevNode == null) | 
 |                 { | 
 |                     itemsNode.PrependChild(itemNode); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     itemsNode.InsertAfter(itemNode, prevNode); | 
 |                 } | 
 |                 prevNode = itemNode; | 
 |             } | 
 |             itemsNode.SetAttribute("count", (items + 1).ToString()); | 
 |         } | 
 |  | 
 |         private int AddDateGroupItems(ExcelPivotTableFieldGroup group, eDateGroupBy GroupBy, DateTime StartDate, DateTime EndDate, int interval) | 
 |         { | 
 |             XmlElement groupItems = group.TopNode.SelectSingleNode("d:fieldGroup/d:groupItems", group.NameSpaceManager) as XmlElement; | 
 |             int items = 2; | 
 |             //First date | 
 |             AddGroupItem(groupItems, "<" + StartDate.ToString("s", CultureInfo.InvariantCulture).Substring(0, 10)); | 
 |  | 
 |             switch (GroupBy) | 
 |             { | 
 |                 case eDateGroupBy.Seconds: | 
 |                 case eDateGroupBy.Minutes: | 
 |                     AddTimeSerie(60, groupItems); | 
 |                     items += 60; | 
 |                     break; | 
 |                 case eDateGroupBy.Hours: | 
 |                     AddTimeSerie(24, groupItems); | 
 |                     items += 24; | 
 |                     break; | 
 |                 case eDateGroupBy.Days: | 
 |                     if (interval == 1) | 
 |                     { | 
 |                         DateTime dt = new DateTime(2008, 1, 1); //pick a year with 366 days | 
 |                         while (dt.Year == 2008) | 
 |                         { | 
 |                             AddGroupItem(groupItems, dt.ToString("dd-MMM")); | 
 |                             dt = dt.AddDays(1); | 
 |                         } | 
 |                         items += 366; | 
 |                     } | 
 |                     else | 
 |                     { | 
 |                         DateTime dt = StartDate; | 
 |                         items = 0; | 
 |                         while (dt < EndDate) | 
 |                         { | 
 |                             AddGroupItem(groupItems, dt.ToString("dd-MMM")); | 
 |                             dt = dt.AddDays(interval); | 
 |                             items++; | 
 |                         } | 
 |                     } | 
 |                     break; | 
 |                 case eDateGroupBy.Months: | 
 |                     AddGroupItem(groupItems, "jan"); | 
 |                     AddGroupItem(groupItems, "feb"); | 
 |                     AddGroupItem(groupItems, "mar"); | 
 |                     AddGroupItem(groupItems, "apr"); | 
 |                     AddGroupItem(groupItems, "may"); | 
 |                     AddGroupItem(groupItems, "jun"); | 
 |                     AddGroupItem(groupItems, "jul"); | 
 |                     AddGroupItem(groupItems, "aug"); | 
 |                     AddGroupItem(groupItems, "sep"); | 
 |                     AddGroupItem(groupItems, "oct"); | 
 |                     AddGroupItem(groupItems, "nov"); | 
 |                     AddGroupItem(groupItems, "dec"); | 
 |                     items += 12; | 
 |                     break; | 
 |                 case eDateGroupBy.Quarters: | 
 |                     AddGroupItem(groupItems, "Qtr1"); | 
 |                     AddGroupItem(groupItems, "Qtr2"); | 
 |                     AddGroupItem(groupItems, "Qtr3"); | 
 |                     AddGroupItem(groupItems, "Qtr4"); | 
 |                     items += 4; | 
 |                     break; | 
 |                 case eDateGroupBy.Years: | 
 |                     if(StartDate.Year>=1900 && EndDate!=DateTime.MaxValue) | 
 |                     { | 
 |                         for (int year = StartDate.Year; year <= EndDate.Year; year++) | 
 |                         { | 
 |                             AddGroupItem(groupItems, year.ToString()); | 
 |                         } | 
 |                         items += EndDate.Year - StartDate.Year+1; | 
 |                     } | 
 |                     break; | 
 |                 default: | 
 |                     throw (new Exception("unsupported grouping")); | 
 |             } | 
 |  | 
 |             //Lastdate | 
 |             AddGroupItem(groupItems, ">" + EndDate.ToString("s", CultureInfo.InvariantCulture).Substring(0, 10)); | 
 |             return items; | 
 |         } | 
 |  | 
 |         private void AddTimeSerie(int count, XmlElement groupItems) | 
 |         { | 
 |             for (int i = 0; i < count; i++) | 
 |             { | 
 |                 AddGroupItem(groupItems, string.Format("{0:00}", i)); | 
 |             } | 
 |         } | 
 |  | 
 |         private void AddGroupItem(XmlElement groupItems, string value) | 
 |         { | 
 |             var s = groupItems.OwnerDocument.CreateElement("s", ExcelPackage.schemaMain); | 
 |             s.SetAttribute("v", value); | 
 |             groupItems.AppendChild(s); | 
 |         } | 
 |         #endregion | 
 |         internal ExcelPivotTableFieldCollectionBase<ExcelPivotTableFieldItem> _items=null; | 
 |         /// <summary> | 
 |         /// Pivottable field Items. Used for grouping. | 
 |         /// </summary> | 
 |         public ExcelPivotTableFieldCollectionBase<ExcelPivotTableFieldItem> Items | 
 |         { | 
 |             get | 
 |             { | 
 |                 if (_items == null) | 
 |                 { | 
 |                     _items = new ExcelPivotTableFieldCollectionBase<ExcelPivotTableFieldItem>(_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); | 
 |                         } | 
 |                     } | 
 |                     //if (_grouping is ExcelPivotTableFieldDateGroup) | 
 |                     //{ | 
 |                     //    ExcelPivotTableFieldDateGroup dtgrp = ((ExcelPivotTableFieldDateGroup)_grouping); | 
 |  | 
 |                     //    ExcelPivotTableFieldItem minItem=null, maxItem=null; | 
 |                     //    foreach (var item in _items) | 
 |                     //    { | 
 |                     //        if (item.X == 0) | 
 |                     //        { | 
 |                     //            minItem = item; | 
 |                     //        } | 
 |                     //        else if (maxItem == null || maxItem.X < item.X) | 
 |                     //        { | 
 |                     //            maxItem = item; | 
 |                     //        } | 
 |                     //    } | 
 |                     //    if (dtgrp.AutoStart) | 
 |                     //    { | 
 |                     //        _items._list.Remove(minItem); | 
 |                     //    } | 
 |                     //    if (dtgrp.AutoEnd) | 
 |                     //    { | 
 |                     //        _items._list.Remove(maxItem); | 
 |                     //    } | 
 |  | 
 |                     //} | 
 |                 } | 
 |                 return _items; | 
 |             } | 
 |         } | 
 |         /// <summary> | 
 |         /// Add numberic grouping to the field | 
 |         /// </summary> | 
 |         /// <param name="Start">Start value</param> | 
 |         /// <param name="End">End value</param> | 
 |         /// <param name="Interval">Interval</param> | 
 |         public void AddNumericGrouping(double Start, double End, double Interval) | 
 |         { | 
 |             ValidateGrouping(); | 
 |             SetNumericGroup(Start, End, Interval); | 
 |         } | 
 |         /// <summary> | 
 |         /// Add a date grouping on this field. | 
 |         /// </summary> | 
 |         /// <param name="groupBy">Group by</param> | 
 |         public void AddDateGrouping(eDateGroupBy groupBy) | 
 |         { | 
 |             AddDateGrouping(groupBy, DateTime.MinValue, DateTime.MaxValue,1); | 
 |         } | 
 |         /// <summary> | 
 |         /// Add a date grouping on this field. | 
 |         /// </summary> | 
 |         /// <param name="groupBy">Group by</param> | 
 |         /// <param name="startDate">Fixed start date. Use DateTime.MinValue for auto</param> | 
 |         /// <param name="endDate">Fixed end date. Use DateTime.MaxValue for auto</param> | 
 |         public void AddDateGrouping(eDateGroupBy groupBy, DateTime startDate, DateTime endDate) | 
 |         { | 
 |             AddDateGrouping(groupBy, startDate, endDate, 1); | 
 |         } | 
 |         /// <summary> | 
 |         /// Add a date grouping on this field. | 
 |         /// </summary> | 
 |         /// <param name="days">Number of days when grouping on days</param> | 
 |         /// <param name="startDate">Fixed start date. Use DateTime.MinValue for auto</param> | 
 |         /// <param name="endDate">Fixed end date. Use DateTime.MaxValue for auto</param> | 
 |         public void AddDateGrouping(int days, DateTime startDate, DateTime endDate) | 
 |         { | 
 |             AddDateGrouping(eDateGroupBy.Days, startDate, endDate, days); | 
 |         } | 
 |         private void AddDateGrouping(eDateGroupBy groupBy, DateTime startDate, DateTime endDate, int groupInterval) | 
 |         { | 
 |             if (groupInterval < 1 || groupInterval >= Int16.MaxValue) | 
 |             { | 
 |                 throw (new ArgumentOutOfRangeException("Group interval is out of range")); | 
 |             } | 
 |             if (groupInterval > 1 && groupBy != eDateGroupBy.Days) | 
 |             { | 
 |                 throw (new ArgumentException("Group interval is can only be used when groupBy is Days")); | 
 |             } | 
 |             ValidateGrouping(); | 
 |  | 
 |             bool firstField = true;             | 
 |             List<ExcelPivotTableField> fields=new List<ExcelPivotTableField>(); | 
 |             //Seconds | 
 |             if ((groupBy & eDateGroupBy.Seconds) == eDateGroupBy.Seconds) | 
 |             { | 
 |                 fields.Add(AddField(eDateGroupBy.Seconds, startDate, endDate, ref firstField)); | 
 |             } | 
 |             //Minutes | 
 |             if ((groupBy & eDateGroupBy.Minutes) == eDateGroupBy.Minutes) | 
 |             { | 
 |                 fields.Add(AddField(eDateGroupBy.Minutes, startDate, endDate, ref firstField)); | 
 |             } | 
 |             //Hours | 
 |             if ((groupBy & eDateGroupBy.Hours) == eDateGroupBy.Hours) | 
 |             { | 
 |                 fields.Add(AddField(eDateGroupBy.Hours, startDate, endDate, ref firstField)); | 
 |             } | 
 |             //Days | 
 |             if ((groupBy & eDateGroupBy.Days) == eDateGroupBy.Days) | 
 |             { | 
 |                 fields.Add(AddField(eDateGroupBy.Days, startDate, endDate, ref firstField, groupInterval)); | 
 |             } | 
 |             //Month | 
 |             if ((groupBy & eDateGroupBy.Months) == eDateGroupBy.Months) | 
 |             { | 
 |                 fields.Add(AddField(eDateGroupBy.Months, startDate, endDate, ref firstField)); | 
 |             } | 
 |             //Quarters | 
 |             if ((groupBy & eDateGroupBy.Quarters) == eDateGroupBy.Quarters) | 
 |             { | 
 |                 fields.Add(AddField(eDateGroupBy.Quarters, startDate, endDate, ref firstField)); | 
 |             } | 
 |             //Years | 
 |             if ((groupBy & eDateGroupBy.Years) == eDateGroupBy.Years) | 
 |             { | 
 |                 fields.Add(AddField(eDateGroupBy.Years, startDate, endDate, ref firstField)); | 
 |             } | 
 |  | 
 |             if (fields.Count > 1) _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/@par", (_table.Fields.Count - 1).ToString()); | 
 |             if (groupInterval != 1) | 
 |             { | 
 |                 _cacheFieldHelper.SetXmlNodeString("d:fieldGroup/d:rangePr/@groupInterval", groupInterval.ToString()); | 
 |             } | 
 |             else | 
 |             { | 
 |                 _cacheFieldHelper.DeleteNode("d:fieldGroup/d:rangePr/@groupInterval"); | 
 |             } | 
 |             _items = null; | 
 |         } | 
 |  | 
 |         private void ValidateGrouping() | 
 |         { | 
 |             if (!(IsColumnField || IsRowField)) | 
 |             { | 
 |                 throw (new Exception("Field must be a row or column field")); | 
 |             } | 
 |             foreach (var field in _table.Fields) | 
 |             { | 
 |                 if (field.Grouping != null) | 
 |                 { | 
 |                     throw (new Exception("Grouping already exists")); | 
 |                 } | 
 |             } | 
 |         } | 
 |         private ExcelPivotTableField AddField(eDateGroupBy groupBy, DateTime startDate, DateTime endDate, ref  bool firstField) | 
 |         { | 
 |             return AddField(groupBy, startDate, endDate, ref firstField,1); | 
 |         } | 
 |         private ExcelPivotTableField AddField(eDateGroupBy groupBy, DateTime startDate, DateTime endDate, ref  bool firstField, int interval) | 
 |         { | 
 |             if (firstField == false) | 
 |             { | 
 |                 //Pivot field | 
 |                 var topNode = _table.PivotTableXml.SelectSingleNode("//d:pivotFields", _table.NameSpaceManager); | 
 |                 var fieldNode = _table.PivotTableXml.CreateElement("pivotField", ExcelPackage.schemaMain); | 
 |                 fieldNode.SetAttribute("compact", "0"); | 
 |                 fieldNode.SetAttribute("outline", "0"); | 
 |                 fieldNode.SetAttribute("showAll", "0"); | 
 |                 fieldNode.SetAttribute("defaultSubtotal", "0"); | 
 |                 topNode.AppendChild(fieldNode); | 
 |  | 
 |                 var field = new ExcelPivotTableField(_table.NameSpaceManager, fieldNode, _table, _table.Fields.Count, Index); | 
 |                 field.DateGrouping = groupBy; | 
 |  | 
 |                 XmlNode rowColFields; | 
 |                 if (IsRowField) | 
 |                 { | 
 |                     rowColFields=TopNode.SelectSingleNode("../../d:rowFields", NameSpaceManager); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     rowColFields = TopNode.SelectSingleNode("../../d:colFields", NameSpaceManager); | 
 |                 } | 
 |  | 
 |                 int fieldIndex, index = 0; | 
 |                 foreach (XmlElement rowfield in rowColFields.ChildNodes) | 
 |                 { | 
 |                     if (int.TryParse(rowfield.GetAttribute("x"), out fieldIndex)) | 
 |                     { | 
 |                         if (_table.Fields[fieldIndex].BaseIndex == BaseIndex) | 
 |                         { | 
 |                             var newElement = rowColFields.OwnerDocument.CreateElement("field", ExcelPackage.schemaMain); | 
 |                             newElement.SetAttribute("x", field.Index.ToString()); | 
 |                             rowColFields.InsertBefore(newElement, rowfield); | 
 |                             break; | 
 |                         } | 
 |                     } | 
 |                     index++; | 
 |                 } | 
 |  | 
 |                 if (IsRowField) | 
 |                 { | 
 |                     _table.RowFields.Insert(field, index); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     _table.ColumnFields.Insert(field, index); | 
 |                 } | 
 |                  | 
 |                 _table.Fields.AddInternal(field); | 
 |  | 
 |                 AddCacheField(field, startDate, endDate, interval); | 
 |                 return field; | 
 |             } | 
 |             else | 
 |             { | 
 |                 firstField = false; | 
 |                 DateGrouping = groupBy; | 
 |                 Compact = false; | 
 |                 SetDateGroup(groupBy, startDate, endDate, interval); | 
 |                 return this; | 
 |             } | 
 |         } | 
 |         private void AddCacheField(ExcelPivotTableField field, DateTime startDate, DateTime endDate, int interval) | 
 |         { | 
 |             //Add Cache definition field. | 
 |             var cacheTopNode = _table.CacheDefinition.CacheDefinitionXml.SelectSingleNode("//d:cacheFields", _table.NameSpaceManager); | 
 |             var cacheFieldNode = _table.CacheDefinition.CacheDefinitionXml.CreateElement("cacheField", ExcelPackage.schemaMain); | 
 |  | 
 |             cacheFieldNode.SetAttribute("name", field.DateGrouping.ToString()); | 
 |             cacheFieldNode.SetAttribute("databaseField", "0"); | 
 |             cacheTopNode.AppendChild(cacheFieldNode); | 
 |             field.SetCacheFieldNode(cacheFieldNode); | 
 |  | 
 |             field.SetDateGroup(field.DateGrouping, startDate, endDate, interval); | 
 |         } | 
 |     } | 
 | } |