6

我有一个使用ClosedXML导出到 Excel 的 C# 应用程序。它工作正常,但只是遇到了一个问题,当我点击:

  var ms = new MemoryStream();
  workbook.SaveAs(ms);

我得到一个例外:

' ', hexadecimal value 0x0B, is an invalid character

它绝对与数据相关,因为我查看某些数据它工作正常,但其他数据会导致此问题。

我怎样才能弄清楚是哪个角色导致了这个问题?另外,一旦我弄清楚了,找到这个字符在我的数据中的位置的最佳方法是什么?

4

3 回答 3

6

由于 ClosedXML 是一个开源项目,因此跟踪错误的最简单方法是从源代码*构建它,然后在调试模式下针对库运行代码。

看到完整的堆栈跟踪后,您应该能够确定错误的来源。很有可能这是 ClosedXML 项目使用 Microsoft XML 库的方式中的一个错误,因为您提到的错误是由 ClosedXML 项目之外的库报告的。


*我下载了项目,并尝试构建它。包中的所有内容都closedxml-79843.zip正确构建。

于 2013-11-26T19:34:42.260 回答
6

由于您放入 ClosedXML 工作表的数据/字符串中有无效字符,因此您必须找到它们并将它们取出。

最简单的解决方案是添加

.Replace((0x0B).ToString(), " ")

到所有字符串以摆脱垂直制表符并用空格替换它们。

于 2013-11-26T10:20:20.850 回答
3

由于 ClosedXML 不会阻止您在值中使用 0x0B 字符,因此您要么必须自己清理它的数据(如@Raidri 建议的那样),要么您可以强制和异常,或者在值时执行字符串替换已设置。我在下面创建了一个示例程序,它使用Castle 的动态代理来包装IXLWorksheetIXLCell接口。首先,我们代理IXLWorksheet值(通过添加新工作表返回,如下例所示,或通过索引现有工作表)。这需要通过方法调用手动完成;从那时起,其他一切都已建立。当访问单元格(通过Cell方法或ActiveCell属性)时,会返回一个代理IXLCell值,该值检查通过Value属性设置的数据和SetValue方法。检查是ValidateMethodInterceptor根据评论完成的。Program.Proxy如果您愿意,可以将整个机制留在您的代码库中,并通过方法中的开关打开/关闭。

作为另一种选择,EPPlus包(具有与 ClosedXML 类似的功能)在遇到 VT 字符时不会崩溃。相反,它用 value 替换它_x00B_。也许转换会更有益?

internal class Program
{
    private static void Main(string[] args)
    {
        var stream = new MemoryStream();

        using (stream)
        {
            using (var workbook = new XLWorkbook())
            {
                using (var worksheet = Proxy(workbook.Worksheets.Add("Sheet 1")))
                {
                    worksheet.Cell("A1").Value = "This  is a test";
                    worksheet.Cell("A2").Value = "This \v is a test";
                    workbook.SaveAs(stream);
                }
            }
        }
    }

    public static IXLWorksheet Proxy(IXLWorksheet target)
    {
        var generator = new ProxyGenerator();
        var options = new ProxyGenerationOptions { Selector = new WorksheetInterceptorSelector() };

        return generator.CreateInterfaceProxyWithTarget<IXLWorksheet>(target, options);
    }
}

public class WorksheetInterceptorSelector : IInterceptorSelector
{
    private static readonly MethodInfo[] methodsToAdjust;
    private readonly ProxyCellInterceptor proxyCellInterceptor = new ProxyCellInterceptor();

    static WorksheetInterceptorSelector()
    {
        methodsToAdjust = typeof(IXLWorksheet).GetMethods()
            .Where(x => x.Name == "Cell")
            .Union(new[] { typeof(IXLWorksheet).GetProperty("ActiveCell").GetGetMethod() })
            .ToArray();
    }

    #region IInterceptorSelector Members

    public IInterceptor[] SelectInterceptors(System.Type type, System.Reflection.MethodInfo method, IInterceptor[] interceptors)
    {
        if (!methodsToAdjust.Contains(method))
            return interceptors;

        return new IInterceptor[] { proxyCellInterceptor }.Union(interceptors).ToArray();
    }

    #endregion
}

public class CellInterceptorSelector : IInterceptorSelector
{
    private static readonly MethodInfo[] methodsToAdjust = new[] { typeof(IXLCell).GetMethod("SetValue"), typeof(IXLCell).GetProperty("Value").GetSetMethod() };

    private ValidateMethodInterceptor proxyCellInterceptor = new ValidateMethodInterceptor();

    #region IInterceptorSelector Members

    public IInterceptor[] SelectInterceptors(System.Type type, MethodInfo method, IInterceptor[] interceptors)
    {
        if (method.IsGenericMethod && method.Name == "SetValue" || methodsToAdjust.Contains(method))
            return new IInterceptor[] { proxyCellInterceptor }.Union(interceptors).ToArray();
        return interceptors;
    }

    #endregion
}

public class ProxyCellInterceptor : IInterceptor
{
    #region IInterceptor Members

    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        //Wrap the return value
        invocation.ReturnValue = Proxy((IXLCell)invocation.ReturnValue);
    }

    #endregion

    public IXLCell Proxy(IXLCell target)
    {
        var generator = new ProxyGenerator();
        var options = new ProxyGenerationOptions { Selector = new CellInterceptorSelector() };

        return generator.CreateInterfaceProxyWithTarget<IXLCell>(target, options);
    }
}

public class ValidateMethodInterceptor : IInterceptor
{
    #region IInterceptor Members

    public void Intercept(IInvocation invocation)
    {
        var value = invocation.Arguments[0];

        //Validate the data as it is being set
        if (value != null && value.ToString().Contains('\v'))
        {
            throw new ArgumentException("Value cannot contain vertical tabs!");
        }

        //Alternatively, you could do a string replace:
        //if (value != null && value.ToString().Contains('\v'))
        //{
        //    invocation.Arguments[0] = value.ToString().Replace("\v", Environment.NewLine);
        //}

        invocation.Proceed();
    }

    #endregion
}
于 2013-12-02T10:09:19.453 回答