【问题标题】:OpenXML to Create a DataTable from Excel - Money Cell Value IncorrectOpenXML 从 Excel 创建数据表 - 货币单元格值不正确
【发布时间】:2014-07-29 02:08:32
【问题描述】:

我正在尝试使用 OpenXML 从 Excel 电子表格创建数据表。使用 Cell.CellValue.innerXml 获取行的单元格值时,用户输入且在电子表格上可见的货币值返回的值与解释的值不同。

电子表格单元格格式为文本,单元格值为 570.81。在 OpenXML 中获取数据时,该值被解释为 570.80999999999995。

此方法用于许多不同的 Excel 导入,其中在构建表格时不知道按标题或列索引的单元格的数据类型。

我看过一些关于 Ecma Office Open XML 文件格式标准的帖子并提到了 numFmtId。这会有价值吗?

我假设由于数据类型是文本并且数字有两位小数,因此必须假设单元格已四舍五入(即使不存在公式)。

我希望有人可以提供正确解释数据的解决方案。

下面是GetCellValue方法:

private static string GetCellValue(SharedStringTablePart stringTablePart, DocumentFormat.OpenXml.Spreadsheet.Cell cell,DocumentFormat.OpenXml.Spreadsheet.Stylesheet styleSheet)
{
    string value = cell.CellValue.InnerXml;

    if (cell.DataType != null && cell.DataType.Value == DocumentFormat.OpenXml.Spreadsheet.CellValues.SharedString)
    {
        return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;  
    }
    else
    {

        if (cell.StyleIndex != null)
        {
            DocumentFormat.OpenXml.Spreadsheet.CellFormat cellFormat = (DocumentFormat.OpenXml.Spreadsheet.CellFormat)styleSheet.CellFormats.ChildElements[(int)cell.StyleIndex.Value];

            int formatId = (int)cellFormat.NumberFormatId.Value;

            if (formatId == 14) //[h]:mm:ss
            {
                DateTime newDate = DateTime.FromOADate(double.Parse(value)); 
                value = newDate.Date.ToString(CultureInfo.InvariantCulture); 
            }
        }
        return value;
    }
}

【问题讨论】:

    标签: c# excel openxml openxml-sdk


    【解决方案1】:

    正如您在问题中指出的那样,格式与使用样式表中的数字格式的单元格值分开存储。

    您应该能够扩展用于格式化日期的代码以包括数字格式化。本质上,您需要获取与您已经在阅读的cellFormat.NumberFormatId.Value 对应的NumberingFormatNumberingFormat 可以在 styleSheet.NumberingFormats 元素中找到。

    一旦你有了这个,你就可以访问NumberingFormatFormatCode属性,然后你可以使用它来格式化你认为合适的数据。

    不幸的是,这种格式使用起来并不那么简单。首先,根据 MSDN here,并非所有格式都写入文件,所以我想您必须将这些格式放在可访问的位置并根据您拥有的 NumberFormatId 加载它们。

    其次,格式字符串的格式与 C# 不兼容,因此您需要进行一些操作。格式布局的详细信息可以在 MSDN here找到。

    我已经拼凑了一些示例代码来处理您在问题中遇到的货币情况,但您可能需要更多地考虑将 excel 格式字符串解析为 C# 格式。

    private static string GetCellValue(SharedStringTablePart stringTablePart, DocumentFormat.OpenXml.Spreadsheet.Cell cell, DocumentFormat.OpenXml.Spreadsheet.Stylesheet styleSheet)
    {
        string value = cell.CellValue.InnerXml;
    
        if (cell.DataType != null && cell.DataType.Value == DocumentFormat.OpenXml.Spreadsheet.CellValues.SharedString)
        {
            return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
        }
        else
        {
            if (cell.StyleIndex != null)
            {
                DocumentFormat.OpenXml.Spreadsheet.CellFormat cellFormat = (DocumentFormat.OpenXml.Spreadsheet.CellFormat)styleSheet.CellFormats.ChildElements[(int)cell.StyleIndex.Value];
    
                int formatId = (int)cellFormat.NumberFormatId.Value;
    
                if (formatId == 14) //[h]:mm:ss
                {
                    DateTime newDate = DateTime.FromOADate(double.Parse(value));
                    value = newDate.Date.ToString(CultureInfo.InvariantCulture);
                }
                else
                {
                    //find the number format
                    NumberingFormat format = styleSheet.NumberingFormats.Elements<NumberingFormat>()
                                    .FirstOrDefault(n => n.NumberFormatId == formatId);
                    double temp;
    
                    if (format != null 
                        && format.FormatCode.HasValue 
                        && double.TryParse(value, out temp))
                    {
                        //we have a format and a value that can be represented as a double
    
                        string actualFormat = GetActualFormat(format.FormatCode, temp);
                        value = temp.ToString(actualFormat);
                    }
                }
            }
            return value;
        }
    }
    
    private static string GetActualFormat(StringValue formatCode, double value)
    {
        //the format is actually 4 formats split by a semi-colon
        //0 for positive, 1 for negative, 2 for zero (I'm ignoring the 4th format which is for text)
        string[] formatComponents = formatCode.Value.Split(';');
    
        int elementToUse = value > 0 ? 0 : (value < 0 ? 1 : 2);
    
        string actualFormat = formatComponents[elementToUse];
    
        actualFormat = RemoveUnwantedCharacters(actualFormat, '_');
        actualFormat = RemoveUnwantedCharacters(actualFormat, '*');
    
        //backslashes are an escape character it seems - I'm ignoring them
        return actualFormat.Replace("\"", ""); ;
    }
    
    private static string RemoveUnwantedCharacters(string excelFormat, char character)
    {
        /*  The _ and * characters are used to control lining up of characters
            they are followed by the character being manipulated so I'm ignoring
            both the _ and * and the character immediately following them.
            Note that this is buggy as I don't check for the preceeding
            backslash escape character which I probably should
            */
        int index = excelFormat.IndexOf(character);
        int occurance = 0;
        while (index != -1)
        {
            //replace the occurance at index using substring
            excelFormat = excelFormat.Substring(0, index) + excelFormat.Substring(index + 2);
            occurance++;
            index = excelFormat.IndexOf(character, index);
        }
        return excelFormat;
    }
    

    给定一张使用货币格式化的值为570.80999999999995 的表格(在英国),我得到的输出是£570.81

    【讨论】:

    • 感谢您抽出宝贵时间提供此信息。自从我发布这个问题以来,我们已经从 Nuget(基于 OpenXML)实现了 Excel 数据阅读器。我们有许多客户使用此工具导入 Excel 数据以支持多达 20 种不同的上传格式,并且许多客户只是将整个工作表格式化为文本。早期测试证明 Excel 数据阅读器对我们来说是一个很好的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2014-01-04
    • 2011-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-17
    相关资源
    最近更新 更多