// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using DocumentFormat.OpenXml.Packaging;
using System.Xml;
using ExcelFormula;

namespace OpenXmlPowerTools
{
    // Classes for "bulk load" of a spreadsheet
    public class MemorySpreadsheet
    {
        private SortedList<int, MemoryRow> rowList;

        public MemorySpreadsheet()
        {
            rowList = new SortedList<int, MemoryRow>();
        }

        public void SetCellValue(int row, int column, object value)
        {
            if (!rowList.ContainsKey(row))
                rowList.Add(row, new MemoryRow(row));
            MemoryRow mr = rowList[row];
            mr.SetCell(new MemoryCell(column, value));
        }

        public void SetCellValue(int row, int column, object value, int styleIndex)
        {
            if (!rowList.ContainsKey(row))
                rowList.Add(row, new MemoryRow(row));
            MemoryRow mr = rowList[row];
            mr.SetCell(new MemoryCell(column, value, styleIndex));
        }

        public object GetCellValue(int row, int column)
        {
            if (!rowList.ContainsKey(row))
                return null;
            MemoryCell cell = rowList[row].GetCell(column);
            if (cell == null)
                return null;
            return cell.GetValue();
        }

        public XElement GetElements()
        {
            XElement root = new XElement(S.sheetData);
            foreach (KeyValuePair<int, MemoryRow> item in rowList)
                root.Add(item.Value.GetElements());
            return root;
        }
    }

    public class MemoryRow
    {
        private int row;
        private SortedList<int, MemoryCell> cellList;

        public MemoryRow(int Row)
        {
            row = Row;
            cellList = new SortedList<int, MemoryCell>();
        }

        public MemoryCell GetCell(int column)
        {
            if (!cellList.ContainsKey(column))
                return null;
            return cellList[column];
        }

        public void SetCell(MemoryCell cell)
        {
            if (cellList.ContainsKey(cell.GetColumn()))
                cellList.Remove(cell.GetColumn());
            cellList.Add(cell.GetColumn(), cell);
        }

        public XElement GetElements()
        {
            XElement root = new XElement(S.row, new XAttribute(NoNamespace.r, row));
            foreach (KeyValuePair<int, MemoryCell> item in cellList)
                root.Add(item.Value.GetElements(row));
            return root;
        }
    }

    public class MemoryCell
    {
        private int column;
        private object cellValue;
        private int styleIndex;

        public MemoryCell(int col, object value)
        {
            column = col;
            cellValue = value;
        }

        public MemoryCell(int col, object value, int style)
        {
            column = col;
            cellValue = value;
            styleIndex = style;
        }

        public int GetColumn()
        {
            return column;
        }
        public object GetValue()
        {
            return cellValue;
        }
        public int GetStyleIndex()
        {
            return styleIndex;
        }

        public XElement GetElements(int row)
        {
            string cellReference = WorksheetAccessor.GetColumnId(column) + row.ToString();
            XElement newCell = null;

            if (cellValue is int || cellValue is double)
                newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XElement(S.v, cellValue.ToString()));
            else if (cellValue is bool)
                newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "b"), new XElement(S.v, (bool)cellValue ? "1" : "0"));
            else if (cellValue is string)
            {
                newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "inlineStr"),
                    new XElement(S._is, new XElement(S.t, cellValue.ToString())));
            }
            if (newCell == null)
                throw new ArgumentException("Invalid cell type.");
            if (styleIndex != 0)
                newCell.Add(new XAttribute(NoNamespace.s, styleIndex));

            return newCell;
        }
    }

    // Static methods to modify worksheets in SpreadsheetML
    public class WorksheetAccessor
    {
        private static XNamespace ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
        private static XNamespace relationshipsns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";

        // Finds the WorksheetPart by sheet name
        public static WorksheetPart GetWorksheet(SpreadsheetDocument document, string worksheetName)
        {
            XDocument workbook = document.WorkbookPart.GetXDocument();
            return (WorksheetPart)document.WorkbookPart.GetPartById(
                workbook.Root.Element(S.sheets).Elements(S.sheet).Where(
                    s => s.Attribute(NoNamespace.name).Value.ToLower().Equals(worksheetName.ToLower()))
                .FirstOrDefault().Attribute(R.id).Value);
        }

        // Creates a new worksheet with the specified name
        public static WorksheetPart AddWorksheet(SpreadsheetDocument document, string worksheetName)
        {
            // Create the empty sheet
            WorksheetPart worksheetPart = document.WorkbookPart.AddNewPart<WorksheetPart>();
            worksheetPart.PutXDocument(new XDocument(
                new XElement(S.worksheet, new XAttribute("xmlns", S.s), new XAttribute(XNamespace.Xmlns + "r", R.r),
                    new XElement(S.sheetData))));
            XDocument wb = document.WorkbookPart.GetXDocument();

            // Generate a unique sheet ID number
            int sheetId = 1;
            if (wb.Root.Element(S.sheets).Elements(S.sheet).Count() != 0)
                sheetId = wb.Root.Element(S.sheets).Elements(S.sheet).Max(n => Convert.ToInt32(n.Attribute(NoNamespace.sheetId).Value)) + 1;

            // If name is null, generate a name based on the sheet ID
            if (worksheetName == null)
                worksheetName = "Sheet" + sheetId.ToString();

            // Create the new sheet element in the workbook
            wb.Root.Element(S.sheets).Add(new XElement(S.sheet,
                new XAttribute(NoNamespace.name, worksheetName),
                new XAttribute(NoNamespace.sheetId, sheetId),
                new XAttribute(R.id, document.WorkbookPart.GetIdOfPart(worksheetPart))));
            document.WorkbookPart.PutXDocument();
            return worksheetPart;
        }

        // Creates a new worksheet with the specified name and contents from a memory spreadsheet
        public static void SetSheetContents(SpreadsheetDocument document, WorksheetPart worksheet, MemorySpreadsheet contents)
        {
            XDocument worksheetXDocument = worksheet.GetXDocument();
            worksheetXDocument.Root.Element(S.sheetData).ReplaceWith(contents.GetElements());
            worksheet.PutXDocument();
        }

        // Translates the column number to the column reference string (e.g. 1 -> A, 2-> B)
        public static string GetColumnId(int columnNumber)
        {
            string result = "";
            do
            {
                result = ((char)((columnNumber - 1) % 26 + (int)'A')).ToString() + result;
                columnNumber = (columnNumber - 1) / 26;
            } while (columnNumber != 0);
            return result;
        }

        // Gets the value of the specified cell
        // Returned object can be double/Double, int/Int32, bool/Boolean or string/String types
        public static object GetCellValue(SpreadsheetDocument document, WorksheetPart worksheet, int column, int row)
        {
            XDocument worksheetXDocument = worksheet.GetXDocument();
            XElement cellValue = GetCell(worksheetXDocument, column, row);

            if (cellValue != null)
            {
                if (cellValue.Attribute(NoNamespace.t) == null)
                {
                    string value = cellValue.Element(S.v).Value;
                    if (value.Contains("."))
                        return Convert.ToDouble(value);
                    return Convert.ToInt32(value);
                }
                switch (cellValue.Attribute(NoNamespace.t).Value)
                {
                    case "b":
                        return (cellValue.Element(S.v).Value == "1");
                    case "s":
                        return GetSharedString(document, System.Convert.ToInt32(cellValue.Element(S.v).Value));
                    case "inlineStr":
                        return cellValue.Element(S._is).Element(S.t).Value;
                }
            }
            return null;
        }

        // Finds the shared string using its index
        private static string GetSharedString(SpreadsheetDocument document, int index)
        {
            XDocument sharedStringsXDocument = document.WorkbookPart.SharedStringTablePart.GetXDocument();
            return sharedStringsXDocument.Root.Elements().ElementAt<XElement>(index).Value;
        }

        // Gets the cell element (c) for the specified cell
        private static XElement GetCell(XDocument worksheet, int column, int row)
        {
            string cellReference = GetColumnId(column) + row.ToString();
            XElement rowElement = worksheet.Root
                   .Element(S.sheetData)
                   .Elements(S.row)
                   .Where(r => r.Attribute(NoNamespace.r).Value.Equals(row.ToString())).FirstOrDefault<XElement>();
            if (rowElement == null)
                return null;
            return rowElement.Elements(S.c).Where(c => c.Attribute(NoNamespace.r).Value.Equals(cellReference)).FirstOrDefault<XElement>();
        }

        // Sets the value for the specified cell
        // The "value" must be double/Double, int/Int32, bool/Boolean or string/String type
        public static void SetCellValue(SpreadsheetDocument document, WorksheetPart worksheet, int row, int column, object value)
        {
            XDocument worksheetXDocument = worksheet.GetXDocument();
            string cellReference = GetColumnId(column) + row.ToString();
            XElement newCell = null;

            if (value is int || value is double)
                newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XElement(S.v, value.ToString()));
            else if (value is bool)
                newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "b"), new XElement(S.v, (bool)value ? "1" : "0"));
            else if (value is string)
            {
                newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "inlineStr"),
                    new XElement(S._is, new XElement(S.t, value.ToString())));
            }
            if (newCell == null)
                throw new ArgumentException("Invalid cell type.");

            SetCell(worksheetXDocument, newCell);
        }

        // Sets the specified cell
        private static void SetCell(XDocument worksheetXDocument, XElement newCell)
        {
            int row;
            int column;
            string cellReference = newCell.Attribute(NoNamespace.r).Value;
            GetRowColumn(cellReference, out row, out column);

            // Find the row containing the cell to add the value to
            XElement rowElement = worksheetXDocument.Root
                .Element(S.sheetData)
                .Elements(S.row)
                .Where(t => t.Attribute(NoNamespace.r).Value == row.ToString())
                .FirstOrDefault();

            if (rowElement == null)
            {
                //row element does not exist
                //create a new one
                rowElement = CreateEmptyRow(row);

                //row elements must appear in order inside sheetData element
                if (worksheetXDocument.Root.Element(S.sheetData).HasElements)
                {   //if there are more rows already defined at sheetData element
                    //find the row with the inmediate higher index for the row containing the cell to set the value to
                    XElement rowAfterElement = FindRowAfter(worksheetXDocument, row);
                    //if there is a row with an inmediate higher index already defined at sheetData
                    if (rowAfterElement != null)
                    {
                        //add the new row before the row with an inmediate higher index
                        rowAfterElement.AddBeforeSelf(rowElement);
                    }
                    else
                    {   //this row is going to be the one with the highest index (add it as the last element for sheetData)
                        worksheetXDocument.Root.Element(S.sheetData).Elements(S.row).Last().AddAfterSelf(rowElement);
                    }
                }
                else
                {   //there are no other rows already defined at sheetData
                    //Add a new row elemento to sheetData
                    worksheetXDocument.Root.Element(S.sheetData).Add(rowElement);
                }

                //Add the new cell to the row Element
                rowElement.Add(newCell);
            }
            else
            {
                //row containing the cell to set the value to is already defined at sheetData
                //look if cell already exist at that row
                XElement currentCell = rowElement
                    .Elements(S.c)
                    .Where(t => t.Attribute(NoNamespace.r).Value == cellReference)
                    .FirstOrDefault();

                if (currentCell == null)
                {   //cell element does not exist at row indicated as parameter
                    //find the inmediate right column for the cell to set the value to
                    XElement columnAfterXElement = FindColumAfter(worksheetXDocument, row, column);
                    if (columnAfterXElement != null)
                    {
                        //Insert the new cell before the inmediate right column
                        columnAfterXElement.AddBeforeSelf(newCell);
                    }
                    else
                    {   //There is no inmediate right cell 
                        //Add the new cell as the last element for the row
                        rowElement.Add(newCell);
                    }
                }
                else
                {
                    //cell alreay exist
                    //replace the current cell with that with the new value
                    currentCell.ReplaceWith(newCell);
                }
            }
        }

        // Finds the row element (r) with a higher number than the specified "row" number
        private static XElement FindRowAfter(XDocument worksheet, int row)
        {
            return worksheet.Root
                .Element(S.sheetData)
                .Elements(S.row)
                .FirstOrDefault(r => System.Convert.ToInt32(r.Attribute(NoNamespace.r).Value) > row);
        }

        // Finds the cell element (c) in the specified row that is after the specified "column" number
        private static XElement FindColumAfter(XDocument worksheet, int row, int column)
        {
            return worksheet.Root
                .Element(S.sheetData)
                .Elements(S.row)
                .FirstOrDefault(r => System.Convert.ToInt32(r.Attribute(NoNamespace.r).Value) == row)
                .Elements(S.c)
                .FirstOrDefault(c => GetColumnNumber(c.Attribute(NoNamespace.r).Value) > GetColumnNumber(GetColumnId(column) + row));
        }

        // Converts the column reference string to a column number (e.g. A -> 1, B -> 2)
        public static int GetColumnNumber(string cellReference)
        {
            int columnNumber = 0;
            foreach (char c in cellReference)
            {
                if (Char.IsLetter(c))
                    columnNumber = columnNumber * 26 + System.Convert.ToInt32(c) - System.Convert.ToInt32('A') + 1;
            }
            return columnNumber;
        }

        // Converts a cell reference string into the row and column numbers for that cell
        // e.g. G5 -> [row = 5, column = 7]
        private static void GetRowColumn(string cellReference, out int row, out int column)
        {
            row = 0;
            column = 0;
            foreach (char c in cellReference)
            {
                if (Char.IsLetter(c))
                    column = column * 26 + System.Convert.ToInt32(c) - System.Convert.ToInt32('A') + 1;
                else
                    row = row * 10 + System.Convert.ToInt32(c) - System.Convert.ToInt32('0');
            }
        }

        // Returns the row and column numbers and worksheet part for the named range
        public static WorksheetPart GetRange(SpreadsheetDocument doc, string rangeName, out int startRow, out int startColumn, out int endRow, out int endColumn)
        {
            XDocument book = doc.WorkbookPart.GetXDocument();
            if (book.Root.Element(S.definedNames) == null)
                throw new ArgumentException("Range name not found: " + rangeName);
            XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
                .Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
            if (element == null)
                throw new ArgumentException("Range name not found: " + rangeName);
            string sheetName = element.Value.Substring(0, element.Value.IndexOf('!'));
            string range = element.Value.Substring(element.Value.IndexOf('!') + 1).Replace("$","");
            int colonIndex = range.IndexOf(':');
            GetRowColumn(range.Substring(0, colonIndex), out startRow, out startColumn);
            GetRowColumn(range.Substring(colonIndex + 1), out endRow, out endColumn);
            return GetWorksheet(doc, sheetName);
        }

        // Sets the named range with the specified range of row and column numbers
        public static void SetRange(SpreadsheetDocument doc, string rangeName, string sheetName, int startRow, int startColumn, int endRow, int endColumn)
        {
            XDocument book = doc.WorkbookPart.GetXDocument();
            if (book.Root.Element(S.definedNames) == null)
                book.Root.Add(new XElement(S.definedNames));
            XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
                .Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
            if (element == null)
            {
                element = new XElement(S.definedName, new XAttribute(NoNamespace.name, rangeName));
                book.Root.Element(S.definedNames).Add(element);
            }
            element.SetValue(String.Format("{0}!${1}${2}:${3}${4}", sheetName, GetColumnId(startColumn), startRow, GetColumnId(endColumn), endRow));
            doc.WorkbookPart.PutXDocument();
        }

        // Sets the end row for the named range
        public static void UpdateRangeEndRow(SpreadsheetDocument doc, string rangeName, int lastRow)
        {
            // Update named range used by pivot table
            XDocument book = doc.WorkbookPart.GetXDocument();
            XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
                .Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
            if (element != null)
            {
                string original = element.Value;
                element.SetValue(original.Substring(0, original.Length - 1) + lastRow.ToString());
            }
            doc.WorkbookPart.PutXDocument();
        }

        // Creates an empty row element (r) with the specified row number
        private static XElement CreateEmptyRow(int row)
        {
            return new XElement(S.row, new XAttribute(NoNamespace.r, row.ToString()));
        }

        public static void ForceCalculateOnLoad(SpreadsheetDocument document)
        {
            XDocument book = document.WorkbookPart.GetXDocument();
            XElement element = book.Root.Element(S.calcPr);
            if (element == null)
            {
                book.Root.Add(new XElement(S.calcPr));
            }
            element.SetAttributeValue(NoNamespace.fullCalcOnLoad, "1");
            document.WorkbookPart.PutXDocument();
        }

        public static void FormulaReplaceSheetName(SpreadsheetDocument document, string oldName, string newName)
        {
            foreach (WorksheetPart sheetPart in document.WorkbookPart.WorksheetParts)
            {
                XDocument sheetDoc = sheetPart.GetXDocument();
                bool changed = false;
                foreach (XElement formula in sheetDoc.Descendants(S.f))
                {
                    ParseFormula parser = new ParseFormula(formula.Value);
                    string newFormula = parser.ReplaceSheetName(oldName, newName);
                    if (newFormula != formula.Value)
                    {
                        formula.SetValue(newFormula);
                        changed = true;
                    }
                }
                if (changed)
                {
                    sheetPart.PutXDocument();
                    ForceCalculateOnLoad(document);
                }
            }
        }

        // Copy all cells in the specified range to a new location
        public static void CopyCellRange(SpreadsheetDocument document, WorksheetPart worksheet, int startRow, int startColumn, int endRow, int endColumn,
            int toRow, int toColumn)
        {
            int rowOffset = toRow - startRow;
            int columnOffset = toColumn - startColumn;
            XDocument worksheetXDocument = worksheet.GetXDocument();
            for (int row = startRow; row <= endRow; row++)
                for (int column = startColumn; column <= endColumn; column++)
                {
                    XElement oldCell = GetCell(worksheetXDocument, column, row);
                    if (oldCell != null)
                    {
                        XElement newCell = new XElement(oldCell);
                        newCell.SetAttributeValue(NoNamespace.r, GetColumnId(column + columnOffset) + (row + rowOffset).ToString());
                        XElement formula = newCell.Element(S.f);
                        if (formula != null)
                        {
                            ParseFormula parser = new ParseFormula(formula.Value);
                            formula.SetValue(parser.ReplaceRelativeCell(rowOffset, columnOffset));
                        }
                        SetCell(worksheetXDocument, newCell);
                    }
                }
            worksheet.PutXDocument();
            ForceCalculateOnLoad(document);
        }

        // Creates a pivot table in the specified sheet using the specified range name
        // The new pivot table will not be configured with any fields in the rows, columns, filters or values
        public static PivotTablePart CreatePivotTable(SpreadsheetDocument document, string rangeName, WorksheetPart sheet)
        {
            int startRow, startColumn, endRow, endColumn;
            WorksheetPart sourceSheet = GetRange(document, rangeName, out startRow, out startColumn, out endRow, out endColumn);

            // Fill out pivotFields element (for PivotTablePart) and cacheFields element (for PivotTableCacheDefinitionPart)
            // with an element for each column in the source range
            XElement pivotFields = new XElement(S.pivotFields, new XAttribute(NoNamespace.count, (endColumn - startColumn + 1).ToString()));
            XElement cacheFields = new XElement(S.cacheFields, new XAttribute(NoNamespace.count, (endColumn - startColumn + 1).ToString()));
            for (int column = startColumn; column <= endColumn; column++)
            {
                pivotFields.Add(new XElement(S.pivotField, new XAttribute(NoNamespace.showAll, "0")));
                XElement sharedItems = new XElement(S.sharedItems);
                // Determine numeric sharedItems values, if any
                object value = GetCellValue(document, sourceSheet, column, startRow + 1);
                if (value is double || value is Int32)
                {
                    bool hasDouble = false;
                    double minValue = Convert.ToDouble(value);
                    double maxValue = Convert.ToDouble(value);
                    if (value is double)
                        hasDouble = true;
                    for (int row = startRow + 1; row <= endRow; row++)
                    {
                        value = GetCellValue(document, sourceSheet, column, row);
                        if (value is double)
                            hasDouble = true;
                        if (Convert.ToDouble(value) < minValue)
                            minValue = Convert.ToDouble(value);
                        if (Convert.ToDouble(value) > maxValue)
                            maxValue = Convert.ToDouble(value);
                    }
                    sharedItems.Add(new XAttribute(NoNamespace.containsSemiMixedTypes, "0"),
                        new XAttribute(NoNamespace.containsString, "0"), new XAttribute(NoNamespace.containsNumber, "1"),
                        new XAttribute(NoNamespace.minValue, minValue.ToString()), new XAttribute(NoNamespace.maxValue, maxValue.ToString()));
                    if (!hasDouble)
                        sharedItems.Add(new XAttribute(NoNamespace.containsInteger, "1"));
                }
                cacheFields.Add(new XElement(S.cacheField, new XAttribute(NoNamespace.name, GetCellValue(document, sourceSheet, column, startRow).ToString()),
                    new XAttribute(NoNamespace.numFmtId, "0"), sharedItems));
            }

            // Fill out pivotCacheRecords element (for PivotTableCacheRecordsPart) with an element
            // for each row in the source range
            XElement pivotCacheRecords = new XElement(S.pivotCacheRecords, new XAttribute("xmlns", S.s),
                new XAttribute(XNamespace.Xmlns + "r", R.r), new XAttribute(NoNamespace.count, (endRow - startRow).ToString()));
            for (int row = startRow + 1; row <= endRow; row++)
            {
                XElement r = new XElement(S.r);

                // Fill the record element with a value from each column in the source row
                for (int column = startColumn; column <= endColumn; column++)
                {
                    object value = GetCellValue(document, sourceSheet, column, row);
                    if (value is String)
                        r.Add(new XElement(S._s, new XAttribute(NoNamespace.v, value.ToString())));
                    else
                        r.Add(new XElement(S.n, new XAttribute(NoNamespace.v, value.ToString())));
                }
                pivotCacheRecords.Add(r);
            }

            // Create pivot table parts with proper links
            PivotTablePart pivotTable = sheet.AddNewPart<PivotTablePart>();
            PivotTableCacheDefinitionPart cacheDef = pivotTable.AddNewPart<PivotTableCacheDefinitionPart>();
            PivotTableCacheRecordsPart records = cacheDef.AddNewPart<PivotTableCacheRecordsPart>();
            document.WorkbookPart.AddPart<PivotTableCacheDefinitionPart>(cacheDef);

            // Set content for the PivotTableCacheRecordsPart and PivotTableCacheDefinitionPart
            records.PutXDocument(new XDocument(pivotCacheRecords));
            cacheDef.PutXDocument(new XDocument(new XElement(S.pivotCacheDefinition, new XAttribute("xmlns", S.s),
                new XAttribute(XNamespace.Xmlns + "r", R.r), new XAttribute(R.id, cacheDef.GetIdOfPart(records)),
                new XAttribute(NoNamespace.recordCount, (endRow - startRow).ToString()),
                    new XElement(S.cacheSource, new XAttribute(NoNamespace.type, "worksheet"),
                        new XElement(S.worksheetSource, new XAttribute(NoNamespace.name, rangeName))),
                    cacheFields)));

            // Create the pivotCache entry in the workbook part
            int cacheId = 1;
            XDocument wb = document.WorkbookPart.GetXDocument();
            if (wb.Root.Element(S.pivotCaches) == null)
                wb.Root.Add(new XElement(S.pivotCaches));
            else
            {
                if (wb.Root.Element(S.pivotCaches).Elements(S.pivotCache).Count() != 0)
                    cacheId = wb.Root.Element(S.pivotCaches).Elements(S.pivotCache).Max(n => Convert.ToInt32(n.Attribute(NoNamespace.cacheId).Value)) + 1;
            }
            wb.Root.Element(S.pivotCaches).Add(new XElement(S.pivotCache,
                new XAttribute(NoNamespace.cacheId, cacheId),
                new XAttribute(R.id, document.WorkbookPart.GetIdOfPart(cacheDef))));
            document.WorkbookPart.PutXDocument();

            // Set the content for the PivotTablePart
            pivotTable.PutXDocument(new XDocument(new XElement(S.pivotTableDefinition, new XAttribute("xmlns", S.s),
                new XAttribute(NoNamespace.name, "PivotTable1"), new XAttribute(NoNamespace.cacheId, cacheId.ToString()),
                new XAttribute(NoNamespace.dataCaption, "Values"),
                new XElement(S.location, new XAttribute(NoNamespace._ref, "A3:C20"),
                    new XAttribute(NoNamespace.firstHeaderRow, "1"), new XAttribute(NoNamespace.firstDataRow, "1"),
                    new XAttribute(NoNamespace.firstDataCol, "0")), pivotFields)));

            return pivotTable;
        }

        public enum PivotAxis { Row, Column, Page };
        public static void AddPivotAxis(SpreadsheetDocument document, WorksheetPart sheet, string fieldName, PivotAxis axis)
        {
            // Create indexed items in cache and definition
            PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
            PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
            PivotTableCacheRecordsPart recordsPart = cacheDefPart.GetPartsOfType<PivotTableCacheRecordsPart>().First();
            XDocument cacheDef = cacheDefPart.GetXDocument();
            int index = Array.FindIndex(cacheDef.Descendants(S.cacheField).ToArray(),
                z => z.Attribute(NoNamespace.name).Value == fieldName);
            XDocument records = recordsPart.GetXDocument();
            List<XElement> values = new List<XElement>();
            foreach (XElement rec in records.Descendants(S.r))
            {
                XElement val = rec.Elements().Skip(index).First();
                int x = Array.FindIndex(values.ToArray(), z => XElement.DeepEquals(z, val));
                if (x == -1)
                {
                    values.Add(val);
                    x = values.Count() - 1;
                }
                val.ReplaceWith(new XElement(S.x, new XAttribute(NoNamespace.v, x)));
            }
            XElement sharedItems = cacheDef.Descendants(S.cacheField).Skip(index).First().Element(S.sharedItems);
            sharedItems.Add(new XAttribute(NoNamespace.count, values.Count()), values);
            recordsPart.PutXDocument();
            cacheDefPart.PutXDocument();

            // Add axis definition to pivot table field
            XDocument pivotTable = pivotTablePart.GetXDocument();
            XElement pivotField = pivotTable.Descendants(S.pivotField).Skip(index).First();
            XElement items = new XElement(S.items, new XAttribute(NoNamespace.count, values.Count() + 1),
                values.OrderBy(z => z.Attribute(NoNamespace.v).Value).Select(z => new XElement(S.item,
                    new XAttribute(NoNamespace.x, Array.FindIndex(values.ToArray(),
                        a => a.Attribute(NoNamespace.v).Value == z.Attribute(NoNamespace.v).Value)))));
            items.Add(new XElement(S.item, new XAttribute(NoNamespace.t, "default")));
            switch (axis)
            {
                case PivotAxis.Column:
                    pivotField.Add(new XAttribute(NoNamespace.axis, "axisCol"), items);
                    // Add to colFields
                    {
                        XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
                        if (fields == null)
                        {
                            fields = new XElement(S.colFields, new XAttribute(NoNamespace.count, 0));
                            XElement rowFields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
                            if (rowFields == null)
                                pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
                            else
                                rowFields.AddAfterSelf(fields);
                        }
                        fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, index)));
                        fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
                    }
                    break;
                case PivotAxis.Row:
                    pivotField.Add(new XAttribute(NoNamespace.axis, "axisRow"), items);
                    // Add to rowFields
                    {
                        XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
                        if (fields == null)
                        {
                            fields = new XElement(S.rowFields, new XAttribute(NoNamespace.count, 0));
                            pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
                        }
                        fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, index)));
                        fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
                    }
                    break;
                case PivotAxis.Page:
                    pivotField.Add(new XAttribute(NoNamespace.axis, "axisPage"), items);
                    // Add to pageFields
                    {
                        XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
                        if (fields == null)
                        {
                            fields = new XElement(S.pageFields, new XAttribute(NoNamespace.count, 0));
                            XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
                            if (prev == null)
                                prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
                            if (prev == null)
                                pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
                            else
                                prev.AddAfterSelf(fields);
                        }
                        fields.Add(new XElement(S.pageField, new XAttribute(NoNamespace.fld, index)));
                        fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
                    }
                    break;
            }
            pivotTablePart.PutXDocument();
            ForcePivotRefresh(cacheDefPart);
        }

        public static void AddDataValueLabel(SpreadsheetDocument document, WorksheetPart sheet, PivotAxis axis)
        {
            PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
            XDocument pivotTable = pivotTablePart.GetXDocument();
            switch (axis)
            {
                case PivotAxis.Column:
                    // Add to colFields
                    {
                        XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
                        if (fields == null)
                        {
                            fields = new XElement(S.colFields, new XAttribute(NoNamespace.count, 0));
                            XElement rowFields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
                            if (rowFields == null)
                                pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
                            else
                                rowFields.AddAfterSelf(fields);
                        }
                        fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, -2)));
                        fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
                    }
                    break;
                case PivotAxis.Row:
                    // Add to rowFields
                    {
                        XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
                        if (fields == null)
                        {
                            fields = new XElement(S.rowFields, new XAttribute(NoNamespace.count, 0));
                            pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
                        }
                        fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, -2)));
                        fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
                    }
                    break;
                case PivotAxis.Page:
                    // Add to pageFields
                    {
                        XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
                        if (fields == null)
                        {
                            fields = new XElement(S.pageFields, new XAttribute(NoNamespace.count, 0));
                            XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
                            if (prev == null)
                                prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
                            if (prev == null)
                                pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
                            else
                                prev.AddAfterSelf(fields);
                        }
                        fields.Add(new XElement(S.pageField, new XAttribute(NoNamespace.fld, -2)));
                        fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
                    }
                    break;
            }
            pivotTablePart.PutXDocument();
            PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
            ForcePivotRefresh(cacheDefPart);
        }

        public static void AddDataValue(SpreadsheetDocument document, WorksheetPart sheet, string fieldName)
        {
            PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
            PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
            XDocument cacheDef = cacheDefPart.GetXDocument();
            int index = Array.FindIndex(cacheDef.Descendants(S.cacheField).ToArray(),
                z => z.Attribute(NoNamespace.name).Value == fieldName);
            XDocument pivotTable = pivotTablePart.GetXDocument();
            XElement pivotField = pivotTable.Descendants(S.pivotField).Skip(index).First();
            pivotField.Add(new XAttribute(NoNamespace.dataField, "1"));
            XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.dataFields);
            if (fields == null)
            {
                fields = new XElement(S.dataFields, new XAttribute(NoNamespace.count, 0));
                XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
                if (prev == null)
                    prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
                if (prev == null)
                    prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
                if (prev == null)
                    prev = pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields);
                prev.AddAfterSelf(fields);
            }
            fields.Add(new XElement(S.dataField, new XAttribute(NoNamespace.name, "Sum of " + fieldName),
                        new XAttribute(NoNamespace.fld, index), new XAttribute(NoNamespace.baseField, 0),
                        new XAttribute(NoNamespace.baseItem, 0)));
            int count = fields.Elements(S.dataField).Count();
            fields.Attribute(NoNamespace.count).Value = count.ToString();
            if (count == 2)
            {   // Only when data field count goes from 1 to 2 do we add a special column to label the data fields
                AddDataValueLabel(document, sheet, PivotAxis.Column);
            }
            pivotTablePart.PutXDocument();
            ForcePivotRefresh(cacheDefPart);
        }

        private static void ForcePivotRefresh(PivotTableCacheDefinitionPart cacheDef)
        {
            XDocument doc = cacheDef.GetXDocument();
            XElement def = doc.Element(S.pivotCacheDefinition);
            if (def.Attribute(NoNamespace.refreshOnLoad) == null)
                def.Add(new XAttribute(NoNamespace.refreshOnLoad, 1));
            else
                def.Attribute(NoNamespace.refreshOnLoad).Value = "1";
            cacheDef.PutXDocument();
        }

        public static void CheckNumberFormat(SpreadsheetDocument document, int fmtID, string formatCode)
        {
            XElement numFmt = new XElement(S.numFmt, new XAttribute(NoNamespace.numFmtId, fmtID.ToString()),
                new XAttribute(NoNamespace.formatCode, formatCode));
            XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
            XElement numFmts = styles.Root.Element(S.numFmts);
            if (numFmts == null)
            {
                styles.Root.Element(S.fonts).AddBeforeSelf(new XElement(S.numFmts, new XAttribute(NoNamespace.count, "0")));
                numFmts = styles.Root.Element(S.numFmts);
            }
            int index = Array.FindIndex(numFmts.Elements(S.numFmt).ToArray(),
                z => XElement.DeepEquals(z, numFmt));
            if (index == -1)
            {
                numFmts.Add(numFmt);
                numFmts.Attribute(NoNamespace.count).Value = numFmts.Elements(S.numFmt).Count().ToString();
                document.WorkbookPart.WorkbookStylesPart.PutXDocument();
            }
        }

        public class ColorInfo
        {
            public enum ColorType { Theme, Indexed };

            private bool Auto;
            private string RGB;
            private int Indexed;
            private int Theme;
            private double Tint;

            public ColorInfo()
            {
                Auto = true;
            }
            public ColorInfo(ColorType type, int value)
            {
                if (type == ColorType.Indexed)
                    Indexed = value;
                else if (type == ColorType.Theme)
                    Theme = value;
            }
            public ColorInfo(int theme, double tint)
            {
                Theme = theme;
                Tint = tint;
            }
            public ColorInfo(string rgb)
            {
                RGB = rgb;
            }

            public XElement GetXElement(XName colorName)
            {
                XElement color = new XElement(colorName);
                if (Auto)
                    color.Add(new XAttribute(NoNamespace.auto, "1"));
                else if (RGB != null)
                    color.Add(new XAttribute(NoNamespace.rgb, RGB));
                else if (Indexed != 0)
                    color.Add(new XAttribute(NoNamespace.indexed, Indexed));
                else
                    color.Add(new XAttribute(NoNamespace.theme, Theme));
                if (Tint != 0)
                    color.Add(new XAttribute(NoNamespace.tint, Tint));
                return color;
            }
        }

        public class Font
        {
            public enum SchemeType { None, Major, Minor };

            public bool Bold { get; set; }
            public ColorInfo Color { get; set; }
            public bool Condense { get; set; }
            public bool Extend { get; set; }
            public int Family { get; set; }
            public bool Italic { get; set; }
            public string Name { get; set; }
            public bool Outline { get; set; }
            public SchemeType Scheme { get; set; }
            public bool Shadow { get; set; }
            public bool StrikeThrough { get; set; }
            public int Size { get; set; }
            public bool Underline { get; set; }

            public XElement GetXElement()
            {
                XElement font = new XElement(S.font);
                if (Bold)
                    font.Add(new XElement(S.b));
                if (Italic)
                    font.Add(new XElement(S.i));
                if (Underline)
                    font.Add(new XElement(S.u));
                if (StrikeThrough)
                    font.Add(new XElement(S.strike));
                if (Condense)
                    font.Add(new XElement(S.condense));
                if (Extend)
                    font.Add(new XElement(S.extend));
                if (Outline)
                    font.Add(new XElement(S.outline));
                if (Shadow)
                    font.Add(new XElement(S.shadow));
                if (Size != 0)
                    font.Add(new XElement(S.sz, new XAttribute(NoNamespace.val, Size.ToString())));
                if (Color != null)
                    font.Add(Color.GetXElement(S.color));
                if (Name != null)
                    font.Add(new XElement(S.name, new XAttribute(NoNamespace.val, Name)));
                if (Family != 0)
                    font.Add(new XElement(S.family, new XAttribute(NoNamespace.val, Family.ToString())));
                switch (Scheme)
                {
                    case SchemeType.Major:
                        font.Add(new XElement(S.scheme, new XAttribute(NoNamespace.val, "major")));
                        break;
                    case SchemeType.Minor:
                        font.Add(new XElement(S.scheme, new XAttribute(NoNamespace.val, "minor")));
                        break;
                }
                return font;
            }
        }

        public static int GetFontIndex(SpreadsheetDocument document, Font f)
        {
            XElement font = f.GetXElement();
            XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
            XElement fonts = styles.Root.Element(S.fonts);
            int index = Array.FindIndex(fonts.Elements(S.font).ToArray(),
                z => XElement.DeepEquals(z, font));
            if (index != -1)
                return index;
            fonts.Add(font);
            fonts.Attribute(NoNamespace.count).Value = fonts.Elements(S.font).Count().ToString();
            document.WorkbookPart.WorkbookStylesPart.PutXDocument();
            return fonts.Elements(S.font).Count() - 1;
        }

        public class PatternFill
        {
            public enum PatternType
            {
                None, Solid, DarkDown, DarkGray, DarkGrid, DarkHorizontal, DarkTrellis, DarkUp, DarkVertical,
                Gray0625, Gray125, LightDown, LightGray, LightGrid, LightHorizontal, LightTrellis, LightUp, LightVertical, MediumGray
            };

            private PatternType Pattern;
            private ColorInfo BgColor;
            private ColorInfo FgColor;

            public PatternFill(PatternType pattern, ColorInfo bgColor, ColorInfo fgColor)
            {
                Pattern = pattern;
                BgColor = bgColor;
                FgColor = fgColor;
            }

            public XElement GetXElement()
            {
                XElement pattern = new XElement(S.patternFill);
                switch (Pattern)
                {
                    case PatternType.DarkDown:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "darkDown"));
                        break;
                    case PatternType.DarkGray:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "darkGray"));
                        break;
                    case PatternType.DarkGrid:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "darkGrid"));
                        break;
                    case PatternType.DarkHorizontal:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "darkHorizontal"));
                        break;
                    case PatternType.DarkTrellis:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "darkTrellis"));
                        break;
                    case PatternType.DarkUp:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "darkUp"));
                        break;
                    case PatternType.DarkVertical:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "darkVertical"));
                        break;
                    case PatternType.Gray0625:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "gray0625"));
                        break;
                    case PatternType.Gray125:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "gray125"));
                        break;
                    case PatternType.LightDown:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "lightDown"));
                        break;
                    case PatternType.LightGray:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "lightGray"));
                        break;
                    case PatternType.LightGrid:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "lightGrid"));
                        break;
                    case PatternType.LightHorizontal:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "lightHorizontal"));
                        break;
                    case PatternType.LightTrellis:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "lightTrellis"));
                        break;
                    case PatternType.LightUp:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "lightUp"));
                        break;
                    case PatternType.LightVertical:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "lightVertical"));
                        break;
                    case PatternType.MediumGray:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "mediumGray"));
                        break;
                    case PatternType.None:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "none"));
                        break;
                    case PatternType.Solid:
                        pattern.Add(new XAttribute(NoNamespace.patternType, "solid"));
                        break;
                }
                if (FgColor != null)
                    pattern.Add(FgColor.GetXElement(S.fgColor));
                if (BgColor != null)
                    pattern.Add(BgColor.GetXElement(S.bgColor));
                return new XElement(S.fill, pattern);
            }
        }

        public class GradientStop
        {
            private double Position;
            private ColorInfo Color;

            public GradientStop(double position, ColorInfo color)
            {
                Position = position;
                Color = color;
            }

            public XElement GetXElement()
            {
                return new XElement(S.stop, new XAttribute(NoNamespace.position, Position), Color.GetXElement(S.color));
            }
        }

        public class GradientFill
        {
            private bool PathGradient;
            private int LinearDegree;
            private double PathTop;
            private double PathLeft;
            private double PathBottom;
            private double PathRight;
            private List<GradientStop> Stops;

            public GradientFill(int degree)
            {
                PathGradient = false;
                LinearDegree = degree;
                Stops = new List<GradientStop>();
            }

            public GradientFill(double top, double left, double bottom, double right)
            {
                PathGradient = true;
                PathTop = top;
                PathLeft = left;
                PathBottom = bottom;
                PathRight = right;
                Stops = new List<GradientStop>();
            }

            public void AddStop(GradientStop stop)
            {
                Stops.Add(stop);
            }

            public XElement GetXElement()
            {
                XElement gradient = new XElement(S.gradientFill);
                if (PathGradient)
                {
                    gradient.Add(new XAttribute(NoNamespace.type, "path"),
                        new XAttribute(NoNamespace.left, PathLeft.ToString()), new XAttribute(NoNamespace.right, PathRight.ToString()),
                        new XAttribute(NoNamespace.top, PathTop.ToString()), new XAttribute(NoNamespace.bottom, PathBottom.ToString()));
                }
                else
                {
                    gradient.Add(new XAttribute(NoNamespace.degree, LinearDegree.ToString()));
                }
                foreach (GradientStop stop in Stops)
                    gradient.Add(stop.GetXElement());
                return new XElement(S.fill, gradient);
            }
        }

        public static int GetFillIndex(SpreadsheetDocument document, PatternFill patternFill)
        {
            return GetFillIndex(document, patternFill.GetXElement());
        }

        public static int GetFillIndex(SpreadsheetDocument document, GradientFill gradientFill)
        {
            return GetFillIndex(document, gradientFill.GetXElement());
        }

        private static int GetFillIndex(SpreadsheetDocument document, XElement fill)
        {
            XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
            XElement fills = styles.Root.Element(S.fills);
            int index = Array.FindIndex(fills.Elements(S.fill).ToArray(),
                z => XElement.DeepEquals(z, fill));
            if (index != -1)
                return index;
            fills.Add(fill);
            fills.Attribute(NoNamespace.count).Value = fills.Elements(S.fill).Count().ToString();
            document.WorkbookPart.WorkbookStylesPart.PutXDocument();
            return fills.Elements(S.fill).Count() - 1;
        }

        public class BorderLine
        {
            public enum LineStyle
            {
                None, DashDot, DashDotDot, Dashed, Dotted, Double, Hair,
                Medium, MediumDashDot, MediumDashDotDot, MediumDashed, SlantDashDot, Thick, Thin
            };
            private LineStyle Style;
            private ColorInfo Color;

            public BorderLine(LineStyle style, ColorInfo color)
            {
                Style = style;
                Color = color;
            }

            public XElement GetXElement(XName name)
            {
                XElement line = new XElement(name);
                switch (Style)
                {
                    case LineStyle.DashDot:
                        line.Add(new XAttribute(NoNamespace.style, "dashDot"));
                        break;
                    case LineStyle.DashDotDot:
                        line.Add(new XAttribute(NoNamespace.style, "dashDotDot"));
                        break;
                    case LineStyle.Dashed:
                        line.Add(new XAttribute(NoNamespace.style, "dashed"));
                        break;
                    case LineStyle.Dotted:
                        line.Add(new XAttribute(NoNamespace.style, "dotted"));
                        break;
                    case LineStyle.Double:
                        line.Add(new XAttribute(NoNamespace.style, "double"));
                        break;
                    case LineStyle.Hair:
                        line.Add(new XAttribute(NoNamespace.style, "hair"));
                        break;
                    case LineStyle.Medium:
                        line.Add(new XAttribute(NoNamespace.style, "medium"));
                        break;
                    case LineStyle.MediumDashDot:
                        line.Add(new XAttribute(NoNamespace.style, "mediumDashDot"));
                        break;
                    case LineStyle.MediumDashDotDot:
                        line.Add(new XAttribute(NoNamespace.style, "mediumDashDotDot"));
                        break;
                    case LineStyle.MediumDashed:
                        line.Add(new XAttribute(NoNamespace.style, "mediumDashed"));
                        break;
                    case LineStyle.SlantDashDot:
                        line.Add(new XAttribute(NoNamespace.style, "slantDashDot"));
                        break;
                    case LineStyle.Thick:
                        line.Add(new XAttribute(NoNamespace.style, "thick"));
                        break;
                    case LineStyle.Thin:
                        line.Add(new XAttribute(NoNamespace.style, "thin"));
                        break;
                }
                line.Add(Color.GetXElement(S.color));
                return line;
            }
        }

        public class Border
        {
            public BorderLine Top { get; set; }
            public BorderLine Bottom { get; set; }
            public BorderLine Left { get; set; }
            public BorderLine Right { get; set; }
            public BorderLine Horizontal { get; set; }
            public BorderLine Vertical { get; set; }
            public BorderLine Diagonal { get; set; }
            public bool DiagonalDown { get; set; }
            public bool DiagonalUp { get; set; }
            public bool Outline { get; set; }

            public XElement GetXElement()
            {
                XElement border = new XElement(S.border);
                if (DiagonalDown)
                    border.Add(new XAttribute(NoNamespace.diagonalDown, "1"));
                if (DiagonalUp)
                    border.Add(new XAttribute(NoNamespace.diagonalUp, "1"));
                if (Outline)
                    border.Add(new XAttribute(NoNamespace.outline, "1"));
                if (Left == null)
                    border.Add(new XElement(S.left));
                else
                    border.Add(Left.GetXElement(S.left));
                if (Right == null)
                    border.Add(new XElement(S.right));
                else
                    border.Add(Right.GetXElement(S.right));
                if (Top == null)
                    border.Add(new XElement(S.top));
                else
                    border.Add(Top.GetXElement(S.top));
                if (Bottom == null)
                    border.Add(new XElement(S.bottom));
                else
                    border.Add(Bottom.GetXElement(S.bottom));
                if (Diagonal == null)
                    border.Add(new XElement(S.diagonal));
                else
                    border.Add(Diagonal.GetXElement(S.diagonal));
                if (Horizontal != null)
                    border.Add(Horizontal.GetXElement(S.horizontal));
                if (Vertical != null)
                    border.Add(Vertical.GetXElement(S.vertical));
                return border;
            }
        }

        public static int GetBorderIndex(SpreadsheetDocument document, Border b)
        {
            XElement border = b.GetXElement();
            XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
            XElement borders = styles.Root.Element(S.borders);
            int index = Array.FindIndex(borders.Elements(S.border).ToArray(),
                z => XElement.DeepEquals(z, border));
            if (index != -1)
                return index;
            borders.Add(border);
            borders.Attribute(NoNamespace.count).Value = borders.Elements(S.border).Count().ToString();
            document.WorkbookPart.WorkbookStylesPart.PutXDocument();
            return borders.Elements(S.border).Count() - 1;
        }

        public static int GetStyleIndex(SpreadsheetDocument document, string styleName)
        {
            XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
            string xfId = styles.Root.Element(S.cellStyles).Elements(S.cellStyle)
                .Where(t => t.Attribute(NoNamespace.name).Value == styleName)
                .FirstOrDefault().Attribute(NoNamespace.xfId).Value;
            XElement cellXfs = styles.Root.Element(S.cellXfs);
            int index = Array.FindIndex(cellXfs.Elements(S.xf).ToArray(),
                z => z.Attribute(NoNamespace.xfId).Value == xfId);
            if (index != -1)
                return index;
            XElement cellStyleXf = styles.Root.Element(S.cellStyleXfs).Elements(S.xf).ToArray()[Convert.ToInt32(xfId)];
            if (cellStyleXf != null)
            {   // Create new xf element under cellXfs
                cellXfs.Add(new XElement(S.xf, new XAttribute(NoNamespace.numFmtId, cellStyleXf.Attribute(NoNamespace.numFmtId).Value),
                    new XAttribute(NoNamespace.fontId, cellStyleXf.Attribute(NoNamespace.fontId).Value),
                    new XAttribute(NoNamespace.fillId, cellStyleXf.Attribute(NoNamespace.fillId).Value),
                    new XAttribute(NoNamespace.borderId, cellStyleXf.Attribute(NoNamespace.borderId).Value),
                    new XAttribute(NoNamespace.xfId, xfId)));
                cellXfs.Attribute(NoNamespace.count).Value = cellXfs.Elements(S.xf).Count().ToString();
                document.WorkbookPart.WorkbookStylesPart.PutXDocument();
                return cellXfs.Elements(S.xf).Count() - 1;
            }

            return 0;
        }

        public class CellAlignment
        {
            public enum Horizontal { General, Center, CenterContinuous, Distributed, Fill, Justify, Left, Right };
            public enum Vertical { Bottom, Center, Distributed, Justify, Top };

            public Horizontal HorizontalAlignment { get; set; }
            public int Indent { get; set; }
            public bool JustifyLastLine { get; set; }
            public int ReadingOrder { get; set; }
            public bool ShrinkToFit { get; set; }
            public int TextRotation { get; set; }
            public Vertical VerticalAlignment { get; set; }
            public bool WrapText { get; set; }

            public CellAlignment()
            {
                HorizontalAlignment = Horizontal.General;
                Indent = 0;
                JustifyLastLine = false;
                ReadingOrder = 0;
                ShrinkToFit = false;
                TextRotation = 0;
                VerticalAlignment = Vertical.Bottom;
                WrapText = false;
            }

            public XElement GetXElement()
            {
                XElement align = new XElement(S.alignment);
                switch (HorizontalAlignment)
                {
                    case Horizontal.Center:
                        align.Add(new XAttribute(NoNamespace.horizontal, "center"));
                        break;
                    case Horizontal.CenterContinuous:
                        align.Add(new XAttribute(NoNamespace.horizontal, "centerContinuous"));
                        break;
                    case Horizontal.Distributed:
                        align.Add(new XAttribute(NoNamespace.horizontal, "distributed"));
                        break;
                    case Horizontal.Fill:
                        align.Add(new XAttribute(NoNamespace.horizontal, "fill"));
                        break;
                    case Horizontal.Justify:
                        align.Add(new XAttribute(NoNamespace.horizontal, "justify"));
                        break;
                    case Horizontal.Left:
                        align.Add(new XAttribute(NoNamespace.horizontal, "left"));
                        break;
                    case Horizontal.Right:
                        align.Add(new XAttribute(NoNamespace.horizontal, "right"));
                        break;
                }
                if (Indent != 0)
                    align.Add(new XAttribute(NoNamespace.indent, Indent));
                if (JustifyLastLine)
                    align.Add(new XAttribute(NoNamespace.justifyLastLine, true));
                if (ReadingOrder != 0)
                    align.Add(new XAttribute(NoNamespace.readingOrder, ReadingOrder));
                if (ShrinkToFit)
                    align.Add(new XAttribute(NoNamespace.shrinkToFit, true));
                if (TextRotation != 0)
                    align.Add(new XAttribute(NoNamespace.textRotation, TextRotation));
                switch (VerticalAlignment)
                {
                    case Vertical.Center:
                        align.Add(new XAttribute(NoNamespace.vertical, "center"));
                        break;
                    case Vertical.Distributed:
                        align.Add(new XAttribute(NoNamespace.vertical, "distributed"));
                        break;
                    case Vertical.Justify:
                        align.Add(new XAttribute(NoNamespace.vertical, "justify"));
                        break;
                    case Vertical.Top:
                        align.Add(new XAttribute(NoNamespace.vertical, "top"));
                        break;
                }
                if (WrapText)
                    align.Add(new XAttribute(NoNamespace.wrapText, true));
                return align;
            }
        }

        public static int GetStyleIndex(SpreadsheetDocument document, int numFmt, int font, int fill, int border, CellAlignment alignment, bool hidden, bool locked)
        {
            XElement xf = new XElement(S.xf, new XAttribute(NoNamespace.numFmtId, numFmt),
                new XAttribute(NoNamespace.fontId, font), new XAttribute(NoNamespace.fillId, fill),
                new XAttribute(NoNamespace.borderId, border), new XAttribute(NoNamespace.xfId, 0),
                new XAttribute(NoNamespace.applyNumberFormat, (numFmt == 0) ? 0 : 1),
                new XAttribute(NoNamespace.applyFont, (font == 0) ? 0 : 1),
                new XAttribute(NoNamespace.applyFill, (fill == 0) ? 0 : 1),
                new XAttribute(NoNamespace.applyBorder, (border == 0) ? 0 : 1));
            if (alignment != null)
            {
                xf.Add(new XAttribute(NoNamespace.applyAlignment, "1"));
                xf.Add(alignment.GetXElement());
            }
            else
                xf.Add(new XAttribute(NoNamespace.applyAlignment, "0"));
            if (hidden || locked)
            {
                XElement prot = new XElement(S.protection);
                if (hidden)
                    prot.Add(new XAttribute(NoNamespace.hidden, true));
                if (locked)
                    prot.Add(new XAttribute(NoNamespace.locked, true));
                xf.Add(prot);
                xf.Add(new XAttribute(NoNamespace.applyProtection, "1"));
            }
            else
                xf.Add(new XAttribute(NoNamespace.applyProtection, "0"));

            XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
            XElement cellXfs = styles.Root.Element(S.cellXfs);
            int index = Array.FindIndex(cellXfs.Elements(S.xf).ToArray(),
                z => XElement.DeepEquals(z, xf));
            if (index != -1)
                return index;
            cellXfs.Add(xf);
            cellXfs.Attribute(NoNamespace.count).Value = cellXfs.Elements(S.xf).Count().ToString();
            document.WorkbookPart.WorkbookStylesPart.PutXDocument();
            return cellXfs.Elements(S.xf).Count() - 1;
        }

        public static void CreateDefaultStyles(SpreadsheetDocument document)
        {
            // Create the style part
            WorkbookStylesPart stylesPart = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();
            stylesPart.PutXDocument(new XDocument(XElement.Parse(
@"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<styleSheet xmlns='http://schemas.openxmlformats.org/spreadsheetml/2006/main'>
  <fonts count='18'>
    <font>
      <sz val='11'/>
      <color theme='1'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color theme='1'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <b/>
      <sz val='18'/>
      <color theme='3'/>
      <name val='Cambria'/>
      <family val='2'/>
      <scheme val='major'/>
    </font>
    <font>
      <b/>
      <sz val='15'/>
      <color theme='3'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <b/>
      <sz val='13'/>
      <color theme='3'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <b/>
      <sz val='11'/>
      <color theme='3'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color rgb='FF006100'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color rgb='FF9C0006'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color rgb='FF9C6500'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color rgb='FF3F3F76'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <b/>
      <sz val='11'/>
      <color rgb='FF3F3F3F'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <b/>
      <sz val='11'/>
      <color rgb='FFFA7D00'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color rgb='FFFA7D00'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <b/>
      <sz val='11'/>
      <color theme='0'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color rgb='FFFF0000'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <i/>
      <sz val='11'/>
      <color rgb='FF7F7F7F'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <b/>
      <sz val='11'/>
      <color theme='1'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
    <font>
      <sz val='11'/>
      <color theme='0'/>
      <name val='Calibri'/>
      <family val='2'/>
      <scheme val='minor'/>
    </font>
  </fonts>
  <fills count='33'>
    <fill>
      <patternFill patternType='none'/>
    </fill>
    <fill>
      <patternFill patternType='gray125'/>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor rgb='FFC6EFCE'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor rgb='FFFFC7CE'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor rgb='FFFFEB9C'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor rgb='FFFFCC99'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor rgb='FFF2F2F2'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor rgb='FFA5A5A5'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor rgb='FFFFFFCC'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='4'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='4' tint='0.79998168889431442'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='4' tint='0.59999389629810485'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='4' tint='0.39997558519241921'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='5'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='5' tint='0.79998168889431442'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='5' tint='0.59999389629810485'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='5' tint='0.39997558519241921'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='6'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='6' tint='0.79998168889431442'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='6' tint='0.59999389629810485'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='6' tint='0.39997558519241921'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='7'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='7' tint='0.79998168889431442'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='7' tint='0.59999389629810485'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='7' tint='0.39997558519241921'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='8'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='8' tint='0.79998168889431442'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='8' tint='0.59999389629810485'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='8' tint='0.39997558519241921'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='9'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='9' tint='0.79998168889431442'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='9' tint='0.59999389629810485'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
    <fill>
      <patternFill patternType='solid'>
        <fgColor theme='9' tint='0.39997558519241921'/>
        <bgColor indexed='65'/>
      </patternFill>
    </fill>
  </fills>
  <borders count='10'>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom/>
      <diagonal/>
    </border>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style='thick'>
        <color theme='4'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style='thick'>
        <color theme='4' tint='0.499984740745262'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style='medium'>
        <color theme='4' tint='0.39997558519241921'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left style='thin'>
        <color rgb='FF7F7F7F'/>
      </left>
      <right style='thin'>
        <color rgb='FF7F7F7F'/>
      </right>
      <top style='thin'>
        <color rgb='FF7F7F7F'/>
      </top>
      <bottom style='thin'>
        <color rgb='FF7F7F7F'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left style='thin'>
        <color rgb='FF3F3F3F'/>
      </left>
      <right style='thin'>
        <color rgb='FF3F3F3F'/>
      </right>
      <top style='thin'>
        <color rgb='FF3F3F3F'/>
      </top>
      <bottom style='thin'>
        <color rgb='FF3F3F3F'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left/>
      <right/>
      <top/>
      <bottom style='double'>
        <color rgb='FFFF8001'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left style='double'>
        <color rgb='FF3F3F3F'/>
      </left>
      <right style='double'>
        <color rgb='FF3F3F3F'/>
      </right>
      <top style='double'>
        <color rgb='FF3F3F3F'/>
      </top>
      <bottom style='double'>
        <color rgb='FF3F3F3F'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left style='thin'>
        <color rgb='FFB2B2B2'/>
      </left>
      <right style='thin'>
        <color rgb='FFB2B2B2'/>
      </right>
      <top style='thin'>
        <color rgb='FFB2B2B2'/>
      </top>
      <bottom style='thin'>
        <color rgb='FFB2B2B2'/>
      </bottom>
      <diagonal/>
    </border>
    <border>
      <left/>
      <right/>
      <top style='thin'>
        <color theme='4'/>
      </top>
      <bottom style='double'>
        <color theme='4'/>
      </bottom>
      <diagonal/>
    </border>
  </borders>
  <cellStyleXfs count='42'>
    <xf numFmtId='0' fontId='0' fillId='0' borderId='0'/>
    <xf numFmtId='0' fontId='2' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='3' fillId='0' borderId='1' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='4' fillId='0' borderId='2' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='5' fillId='0' borderId='3' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='5' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='6' fillId='2' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='7' fillId='3' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='8' fillId='4' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='9' fillId='5' borderId='4' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='10' fillId='6' borderId='5' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='11' fillId='6' borderId='4' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='12' fillId='0' borderId='6' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='13' fillId='7' borderId='7' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='14' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='8' borderId='8' applyNumberFormat='0' applyFont='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='15' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='16' fillId='0' borderId='9' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='9' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='10' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='11' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='12' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='13' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='14' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='15' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='16' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='17' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='18' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='19' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='20' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='21' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='22' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='23' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='24' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='25' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='26' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='27' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='28' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='29' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='30' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='1' fillId='31' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
    <xf numFmtId='0' fontId='17' fillId='32' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
  </cellStyleXfs>
  <cellXfs count='1'>
    <xf numFmtId='0' fontId='0' fillId='0' borderId='0' xfId='0'/>
  </cellXfs>
  <cellStyles count='42'>
    <cellStyle name='20% - Accent1' xfId='19' builtinId='30' customBuiltin='1'/>
    <cellStyle name='20% - Accent2' xfId='23' builtinId='34' customBuiltin='1'/>
    <cellStyle name='20% - Accent3' xfId='27' builtinId='38' customBuiltin='1'/>
    <cellStyle name='20% - Accent4' xfId='31' builtinId='42' customBuiltin='1'/>
    <cellStyle name='20% - Accent5' xfId='35' builtinId='46' customBuiltin='1'/>
    <cellStyle name='20% - Accent6' xfId='39' builtinId='50' customBuiltin='1'/>
    <cellStyle name='40% - Accent1' xfId='20' builtinId='31' customBuiltin='1'/>
    <cellStyle name='40% - Accent2' xfId='24' builtinId='35' customBuiltin='1'/>
    <cellStyle name='40% - Accent3' xfId='28' builtinId='39' customBuiltin='1'/>
    <cellStyle name='40% - Accent4' xfId='32' builtinId='43' customBuiltin='1'/>
    <cellStyle name='40% - Accent5' xfId='36' builtinId='47' customBuiltin='1'/>
    <cellStyle name='40% - Accent6' xfId='40' builtinId='51' customBuiltin='1'/>
    <cellStyle name='60% - Accent1' xfId='21' builtinId='32' customBuiltin='1'/>
    <cellStyle name='60% - Accent2' xfId='25' builtinId='36' customBuiltin='1'/>
    <cellStyle name='60% - Accent3' xfId='29' builtinId='40' customBuiltin='1'/>
    <cellStyle name='60% - Accent4' xfId='33' builtinId='44' customBuiltin='1'/>
    <cellStyle name='60% - Accent5' xfId='37' builtinId='48' customBuiltin='1'/>
    <cellStyle name='60% - Accent6' xfId='41' builtinId='52' customBuiltin='1'/>
    <cellStyle name='Accent1' xfId='18' builtinId='29' customBuiltin='1'/>
    <cellStyle name='Accent2' xfId='22' builtinId='33' customBuiltin='1'/>
    <cellStyle name='Accent3' xfId='26' builtinId='37' customBuiltin='1'/>
    <cellStyle name='Accent4' xfId='30' builtinId='41' customBuiltin='1'/>
    <cellStyle name='Accent5' xfId='34' builtinId='45' customBuiltin='1'/>
    <cellStyle name='Accent6' xfId='38' builtinId='49' customBuiltin='1'/>
    <cellStyle name='Bad' xfId='7' builtinId='27' customBuiltin='1'/>
    <cellStyle name='Calculation' xfId='11' builtinId='22' customBuiltin='1'/>
    <cellStyle name='Check Cell' xfId='13' builtinId='23' customBuiltin='1'/>
    <cellStyle name='Explanatory Text' xfId='16' builtinId='53' customBuiltin='1'/>
    <cellStyle name='Good' xfId='6' builtinId='26' customBuiltin='1'/>
    <cellStyle name='Heading 1' xfId='2' builtinId='16' customBuiltin='1'/>
    <cellStyle name='Heading 2' xfId='3' builtinId='17' customBuiltin='1'/>
    <cellStyle name='Heading 3' xfId='4' builtinId='18' customBuiltin='1'/>
    <cellStyle name='Heading 4' xfId='5' builtinId='19' customBuiltin='1'/>
    <cellStyle name='Input' xfId='9' builtinId='20' customBuiltin='1'/>
    <cellStyle name='Linked Cell' xfId='12' builtinId='24' customBuiltin='1'/>
    <cellStyle name='Neutral' xfId='8' builtinId='28' customBuiltin='1'/>
    <cellStyle name='Normal' xfId='0' builtinId='0'/>
    <cellStyle name='Note' xfId='15' builtinId='10' customBuiltin='1'/>
    <cellStyle name='Output' xfId='10' builtinId='21' customBuiltin='1'/>
    <cellStyle name='Title' xfId='1' builtinId='15' customBuiltin='1'/>
    <cellStyle name='Total' xfId='17' builtinId='25' customBuiltin='1'/>
    <cellStyle name='Warning Text' xfId='14' builtinId='11' customBuiltin='1'/>
  </cellStyles>
  <dxfs count='0'/>
  <tableStyles count='0' defaultTableStyle='TableStyleMedium9' defaultPivotStyle='PivotStyleLight16'/>
</styleSheet>")));
        }

        /// <summary>
        /// Creates a worksheet document and inserts data into it
        /// </summary>
        /// <param name="headerList">List of values that will act as the header</param>
        /// <param name="valueTable">Values for worksheet content</param>
        /// <param name="headerRow">Header row</param>
        /// <returns></returns>
        internal static WorksheetPart Create(SpreadsheetDocument document, List<string> headerList, string[][] valueTable, int headerRow)
        {
            XDocument xDocument = CreateEmptyWorksheet();
            
            for (int i = 0; i < headerList.Count; i++)
            {
                AddValue(xDocument, headerRow, i + 1, headerList[i]);
            }
            int rows = valueTable.GetLength(0);
            int cols = valueTable[0].GetLength(0);

            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    AddValue(xDocument, i + headerRow + 1, j + 1, valueTable[i][j]);
                }
            }
            WorksheetPart part = Add(document, xDocument);
            return part;
        }

        /// <summary>
        /// Creates element structure needed to describe an empty worksheet
        /// </summary>
        /// <returns>Document with contents for an empty worksheet</returns>
        private static XDocument CreateEmptyWorksheet()
        {
            XDocument document =
                new XDocument(
                    new XElement(ns + "worksheet",
                        new XAttribute("xmlns", ns),
                        new XAttribute(XNamespace.Xmlns + "r", relationshipsns),
                        new XElement(ns + "sheetData")
                    )
                );
            return document;
        }

        /// <summary>
        /// Adds a value to a cell inside a worksheet document
        /// </summary>
        /// <param name="worksheet">document to add values</param>
        /// <param name="row">Row</param>
        /// <param name="column">Column</param>
        /// <param name="value">Value to add</param>
        private static void AddValue(XDocument worksheet, int row, int column, string value)
        {
            //Set the cell reference
            string cellReference = GetColumnId(column) + row.ToString();
            double numericValue;
            //Determining if value for cell is text or numeric
            bool valueIsNumeric = double.TryParse(value, out numericValue);

            //Creating the new cell element (markup)
            XElement newCellXElement = valueIsNumeric ?
                    new XElement(ns + "c",
                        new XAttribute("r", cellReference),
                        new XElement(ns + "v", numericValue)
                    )
                :
                    new XElement(ns + "c",
                        new XAttribute("r", cellReference),
                        new XAttribute("t", "inlineStr"),
                        new XElement(ns + "is",
                            new XElement(ns + "t", value)
                        )
                    );

            // Find the row containing the cell to add the value to
            XName rowName = "r";
            XElement rowElement =
                worksheet.Root
                    .Element(ns + "sheetData")
                    .Elements(ns + "row")
                    .Where(
                        t => t.Attribute(rowName).Value == row.ToString()
                    )
                    .FirstOrDefault();

            if (rowElement == null)
            {
                //row element does not exist
                //create a new one
                rowElement = CreateEmptyRow(row);

                //row elements must appear in order inside sheetData element
                if (worksheet.Root
                 .Element(ns + "sheetData").HasElements)
                {   //if there are more rows already defined at sheetData element
                    //find the row with the inmediate higher index for the row containing the cell to set the value to
                    XElement rowAfterElement = FindRowAfter(worksheet, row);
                    //if there is a row with an inmediate higher index already defined at sheetData
                    if (rowAfterElement != null)
                    {
                        //add the new row before the row with an inmediate higher index
                        rowAfterElement.AddBeforeSelf(rowElement);
                    }
                    else
                    {   //this row is going to be the one with the highest index (add it as the last element for sheetData)
                        worksheet.Root.Element(ns + "sheetData").Elements(ns + "row").Last().AddAfterSelf(rowElement);
                    }
                }
                else
                {   //there are no other rows already defined at sheetData
                    //Add a new row elemento to sheetData
                    worksheet
                        .Root
                        .Element(ns + "sheetData")
                        .Add(
                            rowElement //= CreateEmptyRow(row)
                        );
                }

                //Add the new cell to the row Element
                rowElement.Add(newCellXElement);
            }
            else
            {
                //row containing the cell to set the value to is already defined at sheetData
                //look if cell already exist at that row
                XElement currentCellXElement = rowElement
                    .Elements(ns + "c")
                    .Where(
                        t => t.Attribute("r").Value == cellReference
                    ).FirstOrDefault();

                if (currentCellXElement == null)
                {   //cell element does not exist at row indicated as parameter
                    //find the inmediate right column for the cell to set the value to
                    XElement columnAfterXElement = FindColumAfter(worksheet, row, column);
                    if (columnAfterXElement != null)
                    {
                        //Insert the new cell before the inmediate right column
                        columnAfterXElement.AddBeforeSelf(newCellXElement);
                    }
                    else
                    {   //There is no inmediate right cell 
                        //Add the new cell as the last element for the row
                        rowElement.Add(newCellXElement);
                    }
                }
                else
                {
                    //cell alreay exist
                    //replace the current cell with that with the new value
                    currentCellXElement.ReplaceWith(newCellXElement);
                }
            }
        }

        /// <summary>
        /// Adds a given worksheet to the document
        /// </summary>
        /// <param name="worksheet">Worksheet document to add</param>
        /// <returns>Worksheet part just added</returns>
        public static WorksheetPart Add(SpreadsheetDocument doc, XDocument worksheet)
        {
            // Associates base content to a new worksheet part
            WorkbookPart workbook = doc.WorkbookPart;
            WorksheetPart worksheetPart = workbook.AddNewPart<WorksheetPart>();
            worksheetPart.PutXDocument(worksheet);

            // Associates the worksheet part to the workbook part
            XDocument document = doc.WorkbookPart.GetXDocument();
            int sheetId =
                document.Root
                .Element(ns + "sheets")
                .Elements(ns + "sheet")
                .Count() + 1;

            int worksheetCount =
                document.Root
                .Element(ns + "sheets")
                .Elements(ns + "sheet")
                .Where(
                    t =>
                        t.Attribute("name").Value.StartsWith("sheet", StringComparison.OrdinalIgnoreCase)
                )
                .Count() + 1;

            // Adds content to workbook document to reference worksheet document
            document.Root
                .Element(ns + "sheets")
                .Add(
                    new XElement(ns + "sheet",
                        new XAttribute("name", string.Format("sheet{0}", worksheetCount)),
                        new XAttribute("sheetId", sheetId),
                        new XAttribute(relationshipsns + "id", workbook.GetIdOfPart(worksheetPart))
                    )
                );
            doc.WorkbookPart.PutXDocument();
            return worksheetPart;
        }
    }
}
