OpenXml Excel:在邮件地址后的任何单词中抛出错误

问题描述 投票:0回答:5

我使用 OpenXml 读取 Excel 文件。一切正常,但如果电子表格包含一个包含地址邮件的单元格,其后有一个空格和另一个单词,例如:

[电子邮件受保护] abc

它在打开电子表格时立即引发异常:

var _doc = SpreadsheetDocument.Open(_filePath, false); 

例外:

DocumentFormat.OpenXml.Packaging.OpenXmlPackageException
附加信息:
无效的超链接:格式错误的 URI 嵌入为 文档中的超链接。

c# excel openxml
5个回答
13
投票

OpenXml 论坛上有一个与此问题相关的未决问题:格式错误的超链接导致异常

在帖子中,他们谈到了在 Word 文档中遇到格式错误的“mailto:”超链接的问题。

他们在这里提出了一种解决方法:格式错误的超链接异常的解决方法

解决方法本质上是一个小型控制台应用程序,它可以找到无效的 URL 并将其替换为硬编码值;这是他们的示例中进行替换的代码片段;您可以扩充此代码以尝试更正传递的brokenUri:

private static Uri FixUri(string brokenUri)
{
    return new Uri("http://broken-link/");
}

我遇到的问题实际上是一个 Excel 文档(就像你一样),它与格式错误的 http URL 有关;我惊喜地发现他们的代码与我的 Excel 文件运行得很好。

这是完整的解决方法源代码,以防万一这些链接之一将来消失:

 void Main(string[] args)
    {
        var fileName = @"C:\temp\corrupt.xlsx";
        var newFileName = @"c:\temp\Fixed.xlsx";
        var newFileInfo = new FileInfo(newFileName);

        if (newFileInfo.Exists)
            newFileInfo.Delete();

        File.Copy(fileName, newFileName);

        WordprocessingDocument wDoc;
        try
        {
            using (wDoc = WordprocessingDocument.Open(newFileName, true))
            {
                ProcessDocument(wDoc);
            }
        }
        catch (OpenXmlPackageException e)
        {
            e.Dump();
            if (e.ToString().Contains("The specified package is not valid."))
            {
                using (FileStream fs = new FileStream(newFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                {
                    UriFixer.FixInvalidUri(fs, brokenUri => FixUri(brokenUri));
                }               
            }
        }
    }

    private static Uri FixUri(string brokenUri)
    {
        brokenUri.Dump();
        return new Uri("http://broken-link/");
    }

    private static void ProcessDocument(WordprocessingDocument wDoc)
    {
        var elementCount = wDoc.MainDocumentPart.Document.Descendants().Count();
        Console.WriteLine(elementCount);
    }
}

public static class UriFixer
{
    public static void FixInvalidUri(Stream fs, Func<string, Uri> invalidUriHandler)
    {
        XNamespace relNs = "http://schemas.openxmlformats.org/package/2006/relationships";
        using (ZipArchive za = new ZipArchive(fs, ZipArchiveMode.Update))
        {
            foreach (var entry in za.Entries.ToList())
            {
                if (!entry.Name.EndsWith(".rels"))
                    continue;
                bool replaceEntry = false;
                XDocument entryXDoc = null;
                using (var entryStream = entry.Open())
                {
                    try
                    {
                        entryXDoc = XDocument.Load(entryStream);
                        if (entryXDoc.Root != null && entryXDoc.Root.Name.Namespace == relNs)
                        {
                            var urisToCheck = entryXDoc
                                .Descendants(relNs + "Relationship")
                                .Where(r => r.Attribute("TargetMode") != null && (string)r.Attribute("TargetMode") == "External");
                            foreach (var rel in urisToCheck)
                            {
                                var target = (string)rel.Attribute("Target");
                                if (target != null)
                                {
                                    try
                                    {
                                        Uri uri = new Uri(target);
                                    }
                                    catch (UriFormatException)
                                    {
                                        Uri newUri = invalidUriHandler(target);
                                        rel.Attribute("Target").Value = newUri.ToString();
                                        replaceEntry = true;
                                    }
                                }
                            }
                        }
                    }
                    catch (XmlException)
                    {
                        continue;
                    }
                }
                if (replaceEntry)
                {
                    var fullName = entry.FullName;
                    entry.Delete();
                    var newEntry = za.CreateEntry(fullName);
                    using (StreamWriter writer = new StreamWriter(newEntry.Open()))
                    using (XmlWriter xmlWriter = XmlWriter.Create(writer))
                    {
                        entryXDoc.WriteTo(xmlWriter);
                    }
                }
            }
        }
    }

13
投票

@RMD 的修复效果非常好。我已经使用它很多年了。但有一个新的修复。

您可以在问题#793变更日志

中查看修复程序

将 OpenXML 升级到 2.12.0。

右键单击解决方案并选择管理 NuGet 包

实施修复

  1. 进行单元测试很有帮助。使用错误的电子邮件地址(例如 test@gmail,com)创建一个 Excel 文件。 (注意逗号而不是点)。
  2. 确保您打开的流和对 SpreadsheetDocument.Open 的调用允许读取和写入。
  3. 你需要实现一个RelationshipErrorHandlerFactory并在打开时在选项中使用它。这是我使用的代码:
    public class UriRelationshipErrorHandler : RelationshipErrorHandler
    {
        public override string Rewrite(Uri partUri, string id, string uri)
        {
            return "https://broken-link";
        }
    }
  1. 那么打开文档的时候就需要这样使用:
    var openSettings = new OpenSettings
    {
        RelationshipErrorHandlerFactory = package =>
        {
            return new UriRelationshipErrorHandler();
        }
    };
    using var document = SpreadsheetDocument.Open(stream, true, openSettings);

此解决方案的好处之一是它不需要您创建文件的临时“固定”版本,并且代码少得多。


1
投票

不幸的是,您必须以 zip 打开文件并替换损坏的超链接的解决方案对我没有帮助。

我只是想知道当你的目标框架是 4.0 时,即使你唯一安装的 .Net Framework 版本是 4.7.2,它如何也能正常工作。 我发现

System.UriParser
中有一个私有静态字段,用于选择 URI RFC 规范的版本。因此可以将其设置为 V2,因为它是为 .net 4.0 和更低版本的 .Net Framework 设置的。唯一的问题是
private static readonly

也许有人会想为整个应用程序全局设置它。但我写了

UriQuirksVersionPatcher
它将更新此版本并在 Dispose 方法中将其恢复。它显然不是线程安全的,但对于我的目的来说是可以接受的。

using System;
using System.Diagnostics;
using System.Reflection;

namespace BarCap.RiskServices.RateSubmissions.Utility
{
#if (NET20 || NET35 || NET40)
        public class UriQuirksVersionPatcher : IDisposable
        {
            public void Dispose()
            {
            }
        }
#else

    public class UriQuirksVersionPatcher : IDisposable
    {
        private const string _quirksVersionFieldName = "s_QuirksVersion"; //See Source\ndp\fx\src\net\System\_UriSyntax.cs in NexFX sources
        private const string _uriQuirksVersionEnumName = "UriQuirksVersion";
        /// <code>
        /// private enum UriQuirksVersion
        /// {
        ///     V1 = 1, // RFC 1738 - Not supported
        ///     V2 = 2, // RFC 2396
        ///     V3 = 3, // RFC 3986, 3987
        /// }
        /// </code>
        private const string _oldQuirksVersion = "V2";

        private static readonly Lazy<FieldInfo> _targetFieldInfo;
        private static readonly Lazy<int?> _patchValue;
        private readonly int _oldValue;
        private readonly bool _isEnabled;

        static UriQuirksVersionPatcher()
        {
            var targetType = typeof(UriParser);
            _targetFieldInfo = new Lazy<FieldInfo>(() => targetType.GetField(_quirksVersionFieldName, BindingFlags.Static | BindingFlags.NonPublic));
            _patchValue = new Lazy<int?>(() => GetUriQuirksVersion(targetType));
        }

        public UriQuirksVersionPatcher()
        {
            int? patchValue = _patchValue.Value;
            _isEnabled = patchValue.HasValue;

            if (!_isEnabled) //Disabled if it failed to get enum value
            {
                return;
            }

            int originalValue = QuirksVersion;
            _isEnabled = originalValue != patchValue;

            if (!_isEnabled) //Disabled if value is proper
            {
                return;
            }

            _oldValue = originalValue;
            QuirksVersion = patchValue.Value;
        }

        private int QuirksVersion
        {
            get
            {
                return (int)_targetFieldInfo.Value.GetValue(null);
            }
            set
            {
                _targetFieldInfo.Value.SetValue(null, value);
            }
        }

        private static int? GetUriQuirksVersion(Type targetType)
        {
            int? result = null;
            try
            {
                result = (int)targetType.GetNestedType(_uriQuirksVersionEnumName, BindingFlags.Static | BindingFlags.NonPublic)
                                        .GetField(_oldQuirksVersion, BindingFlags.Static | BindingFlags.Public)
                                        .GetValue(null);
            }
            catch
            {
#if DEBUG

                Debug.WriteLine("ERROR: Failed to find UriQuirksVersion.V2 enum member.");
                throw;

#endif
            }
            return result;
        }

        public void Dispose()
        {
            if (_isEnabled)
            {
                QuirksVersion = _oldValue;
            }
        }
    }
#endif
}

用途:

using(new UriQuirksVersionPatcher())
{
    using(var document = SpreadsheetDocument.Open(fullPath, false))
    {
       //.....
    }
}

附注后来我发现有人已经实现了这个pathcher:https://github.com/google/google-api-dotnet-client/blob/master/Src/Support/Google.Apis.Core/Util/UriPatcher.cs


0
投票

我没有使用过 OpenXml,但如果没有特定原因使用它,那么我强烈推荐 LinqToExcel 中的 LinqToExcel。代码示例在这里:

var sheet = new ExcelQueryFactory("filePath");
var allRows = from r in sheet.Worksheet() select r;
foreach (var r in allRows) {
var cella = r["Header"].ToString();
}

0
投票

就我而言,Excel 数据采用表格格式。因此,我复制所有数据,然后将其作为值粘贴到空单元格中,然后删除所有之前的数据,然后我可以正确导入数据。

© www.soinside.com 2019 - 2024. All rights reserved.