NPOI C# populate Excel spreadsheet containing a table and XML Map

Nicholas Webb 0 Reputation points
2023-04-21T13:48:13.16+00:00

Using C# and NPOI, I'm trying to import data into an existing Excel spreadsheet that contains a table and an XML Map as below. enter image description here

When I try to save and close the spreadsheet at end of process I get a NullReferenceException as below, despite the templateWorkbook &/or fsOut objects not being null: enter image description here

enter image description here

The process runs successfully if I manually remove the XML Map from the template spreadsheet prior to running the code, see sample output below: enter image description here

However, the XML Map is crucial to the next step of the process, so I must work with the version that contains the XML Map. Why would the XML Map prevent the saving and closing of the file? Am I missing something? Do I have to somehow sync the XML Map with the table in the spreadsheet? Any help would be greatly appreciated. Here's the code:

using NPOI.POIFS.NIO;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.XWPF.UserModel;
using NPOI.XSSF.UserModel.Extensions;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using NPOI.SS.Util;
using System.Diagnostics;

namespace ExportSQLToExcel
{
    internal static class SQLToXLSXExporter
    {
        private const int HDRCOUNT = 3;

        internal static void ExportDataToXLSX(string? templateFileName, Int64 iRowsPerChunk)
        {
            if (File.Exists(templateFileName) == false)
            {
                throw (new FileNotFoundException(string.Format("File not found: {0}", templateFileName)));
            }

            string connectionString = @"Server=localhost; Database=UK1TST;Trusted_Connection=True;";

            var builder = new SqlConnectionStringBuilder(connectionString);

            Console.WriteLine(String.Format("Server: {0}", builder.DataSource));
            Console.WriteLine(String.Format("Database: {0}", builder.InitialCatalog));

            SqlConnection sqlCon = new SqlConnection(connectionString);
            sqlCon.Open();

            SqlCommand sqlCmd = new SqlCommand(@"SELECT * FROM [GLPOST]", sqlCon);
            SqlDataReader reader = sqlCmd.ExecuteReader();

            string workingFolder = @"D:\MyDocsFromRodNuc\Temp\";
            int iChunkNum = 1;
            string? chunkName = Path.Combine(workingFolder, string.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(templateFileName), iChunkNum.ToString().PadLeft(2, '0'), Path.GetExtension(templateFileName)));
            string? templateFileNameTemp = Path.Combine(workingFolder, Path.GetFileName(templateFileName));

            // copy in the empty template
            File.Copy(templateFileName, templateFileNameTemp, true);

            XSSFWorkbook? templateWorkbook = null;

            using (FileStream fs = new FileStream(templateFileNameTemp, FileMode.Open, FileAccess.Read))
            {
                templateWorkbook = new XSSFWorkbook(fs);
            }
            string sheetName = "Gen. Journal Line";
            ISheet sheet = templateWorkbook.GetSheet(sheetName) ?? templateWorkbook.CreateSheet(sheetName);
            XSSFSheet XSheet = (XSSFSheet)sheet;
            var lstTables = XSheet.GetTables();
            XSSFTable? XTable = null;
            
            foreach (var item in lstTables)
            {
                XTable = (XSSFTable)item;
                break;
            }

            if (XTable == null)
            {
                throw (new ApplicationException(string.Format("No table found in spreadsheet: {0}", templateFileNameTemp)));
            }

            var inARefs = XTable.GetCellReferences();
            string sAreaRef = inARefs.FormatAsString();
            Debug.WriteLine(sAreaRef);

            // get the starting row number for the appending of extra rows
            // int iStartRow = XTable.EndRowIndex + 1;
            int iStartRow = XTable.EndRowIndex;

            // START OF: Build list of input field names from Sage DB Query
            string[] inputFieldNames = new string[reader.FieldCount];
            for (int i = 0; i < reader.FieldCount; i++)
                inputFieldNames[i] = reader.GetName(i);
            // END OF: Build list of input field names from Sage DB Query

            object[] inputFieldValues = new object[reader.FieldCount];

            int iRowCount = iStartRow;
            int iInputColNum = 0;
            int iOutputColNum = 0;
            while (reader.Read())
            {
                if (iRowCount == iRowsPerChunk)
                {
                    // save & close Excel Workbook (chunk)
                    using (FileStream fsOut = new FileStream(chunkName, FileMode.Create, FileAccess.Write))
                    {
                        templateWorkbook.Write(fsOut, false);
                    }

                    iChunkNum++;
                    iRowCount = iStartRow;
                    chunkName = Path.Combine(workingFolder, string.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(templateFileName), iChunkNum.ToString().PadLeft(2, '0'), Path.GetExtension(templateFileName)));

                    // copy in the empty template (again)
                    File.Copy(templateFileName, templateFileNameTemp, true);

                    using (FileStream fs = new FileStream(templateFileNameTemp, FileMode.Open, FileAccess.Read))
                    {
                        templateWorkbook = new XSSFWorkbook(fs);
                    }
                }

                // read a row from the DB Query
                reader.GetValues(inputFieldValues);

                // Write row
                IRow dataRow = XSheet.CreateRow(iRowCount);

                string curInputFieldName = string.Empty;
                NPOI.SS.UserModel.ICell? cell = null;
                iInputColNum = -1;
                string sItem = string.Empty;

                // populate some fixed key field values.

                cell = dataRow.CreateCell(0); // Journal Template Name
                cell.SetCellType(CellType.String);
                cell.SetCellValue("GENERAL");

                cell = dataRow.CreateCell(1); // Journal Batch Name
                cell.SetCellType(CellType.String);
                cell.SetCellValue("DEFAULT");

                cell = dataRow.CreateCell(2); // Line No.
                cell.SetCellType(CellType.Numeric);
                cell.SetCellValue(iRowCount);

                // for each input column
                foreach (var item in inputFieldValues)
                {
                    iInputColNum++;
                    curInputFieldName = inputFieldNames[iInputColNum];
                    if (string.IsNullOrEmpty(item.ToString()))
                    {
                        sItem = string.Empty;
                    }
                    else 
                    {
                        sItem = item.ToString().TrimEnd();
                    }

                    switch (curInputFieldName)
                    {
                        case "ACCTID": case "JNLDTLDESC":
                            iOutputColNum = GetOutputColumNumber(curInputFieldName);
                            cell = dataRow.CreateCell(iOutputColNum);
                            cell.SetCellType(CellType.String);
                            cell.SetCellValue(sItem);
                            break;
                        case "DOCDATE":
                            iOutputColNum = GetOutputColumNumber(curInputFieldName);
                            cell = dataRow.CreateCell(iOutputColNum);
                            cell.SetCellType(CellType.String);
                            cell.SetCellValue(ProcessSageDate(sItem));
                            break;
                    }
                }
                if (iRowCount != iStartRow)
                {
                    sAreaRef = IncrementAreaRefRow(sAreaRef);
                }
                iRowCount++;
            }

            XTable.SetCellReferences(new NPOI.SS.Util.AreaReference(sAreaRef, NPOI.SS.SpreadsheetVersion.EXCEL2007));
            XTable.UpdateReferences();

            using (FileStream fsOut = new FileStream(chunkName, FileMode.CreateNew, FileAccess.Write))
            {
                templateWorkbook.Write(fsOut);
                fsOut.Close();
                templateWorkbook.Close();
            }
            reader.Close();
            sqlCon.Close();

        }

        private static int GetOutputColumNumber(string curInputFieldName)
        {
            int iColumnNum = -1;
            switch (curInputFieldName.ToUpper())
            {
                case "ACCTID":
                    iColumnNum = 4;
                    break;
                case "JNLDTLDESC":
                    iColumnNum = 8;
                    break;
                case "DOCDATE":
                    iColumnNum = 5;
                    break;
            }
            return (iColumnNum);
        }

        private static string IncrementAreaRefRow(string sAreaRef)
        {
            // e.g. A3:M4 to A3:M5
            // &/OR:
            // e.g. A3:M14 to A3:M15
            // &/OR:
            // e.g. A3:M214 to A3:M215

            string[] parts = sAreaRef.Split(':');
            string endRef = parts[1];
            var sbEndRef = new StringBuilder();
            sbEndRef.Length = 0;
            CharEnumerator chEnum = endRef.GetEnumerator();
            while (chEnum.MoveNext())
            {
                if (char.IsDigit(chEnum.Current))
                {
                    sbEndRef.Append(chEnum.Current);
                }
            }
            // string sRowNum = sAreaRef.Substring(sAreaRef.Length - 1);
            int iRowNum = int.Parse(sbEndRef.ToString());
            iRowNum++;
            var sbAreaRefOut = new StringBuilder();
            sbAreaRefOut.Length = 0;
            sbAreaRefOut.Append(sAreaRef.Substring(0, sAreaRef.Length - sbEndRef.Length));
            sbAreaRefOut.Append(iRowNum.ToString());
            return (sbAreaRefOut.ToString());
        }

        private static string ProcessSageDate(string? inputDate)
        {
            if (string.IsNullOrEmpty(inputDate) ) 
            {
                inputDate = DateTime.Now.ToString("dd/mm/yyyy");
            }

            // (e.g.) 20191014 to 14/10/2019
            StringBuilder lsb = new StringBuilder();
            lsb.Length = 0;
            lsb.Append(inputDate.Substring(inputDate.Length - 2)); // dd
            lsb.Append("/");
            lsb.Append(inputDate.Substring(4,2)); // mm
            lsb.Append("/");
            lsb.Append(inputDate.Substring(0, 4)); // yyyy

            return (lsb.ToString());
        }

    }
}

Excel
Excel
A family of Microsoft spreadsheet software with tools for analyzing, charting, and communicating data.
1,541 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,387 questions
Office Development
Office Development
Office: A suite of Microsoft productivity software that supports common business tasks, including word processing, email, presentations, and data management and analysis.Development: The process of researching, productizing, and refining new or existing technologies.
3,583 questions
{count} votes