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

18

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

abc@abc.com abc

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

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

异常:

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


https://github.com/OfficeDev/Open-XML-SDK/issues/715 - lindexi
4个回答

13

OpenXml论坛上有一个与此问题相关的未解决问题:Malformed Hyperlink causes exception

在帖子中,他们谈到在Word文档中遇到了一个格式不正确的“mailto:”超链接的问题。

他们在这里提出了一个解决方法:Workaround for malformed hyperlink exception

这个解决方法本质上是一个小型控制台应用程序,它会定位无效的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. 拥有单元测试非常有帮助。创建一个包含错误电子邮件地址的Excel文件,例如test@gmail,com。(请注意逗号代替了点号)。
  2. 确保您打开的流和对SpreadsheetDocument.Open的调用允许读取和写入。
  3. 您需要实现一个RelationshipErrorHandlerFactory并在打开选项中使用它。以下是我使用的代码:
    public class UriRelationshipErrorHandler : RelationshipErrorHandler
    {
        public override string Rewrite(Uri partUri, string id, string uri)
        {
            return "https://broken-link";
        }
    }

4. 然后您需要在打开文档时使用它,如下所示:
    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))
    {
       //.....
    }
}

P.S. 后来我发现已经有人实现了这个修补程序: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();
}

我有一个具体的理由。它不是按字段和行处理Excel“表格”,而是通过地址单元格和格式等来处理Excel。 - dovid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接