blob: 05570f2d18ed1b237d27491d68abc8ea13f39af9 [file] [log] [blame]
/*******************************************************************************
* 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 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");
}
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;
}
}
}