programing

OpenXml Excel: 메일 주소 뒤에 있는 모든 단어에 오류 발생

powerit 2023. 6. 27. 22:38
반응형

OpenXml Excel: 메일 주소 뒤에 있는 모든 단어에 오류 발생

OpenXml을 사용하여 Excel 파일을 읽습니다. 모두 정상적으로 작동하지만 스프레드시트에 주소 메일이 있는 셀이 하나 있고 그 뒤에 공백과 다음과 같은 다른 단어가 있으면 됩니다.

abc@abc.com abc

스프레드시트를 열 때 즉시 예외가 발생합니다.

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

예외:

문서 형식입니다.OpenXml.포장.OpenXml 패키지예외.
추가 정보:
잘못된 하이퍼링크: 잘못된 형식의 URI가 문서에 하이퍼링크로 포함되어 있습니다.

OpenXml 포럼에 이 문제와 관련된 미해결 문제가 있습니다.잘못된 형식의 하이퍼링크로 인해 예외 발생

게시물에서 그들은 Word 문서 내의 잘못된 형식의 "mailto:" 하이퍼링크와 함께 이 문제가 발생하는 것에 대해 이야기합니다.

여기서 해결 방법을 제안합니다. 잘못된 형식의 하이퍼링크 예외에 대한 해결 방법입니다.

해결 방법은 기본적으로 잘못된 URL을 찾아 하드 코딩된 값으로 교체하는 작은 콘솔 응용 프로그램입니다. 여기 교체를 수행하는 샘플의 코드 스니펫이 있습니다. 이 코드를 확대하여 전달된 broakedUri를 수정할 수 있습니다.

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);
                    }
                }
            }
        }
    }

@RMD에 의한 수정은 잘 작동합니다.저는 몇 년 동안 그것을 사용해 왔습니다.하지만 새로운 해결책이 있습니다.

793호에 대한 변경 로그에서 수정 사항을 확인할 수 있습니다.

OpenXML을 2.12.0으로 업그레이드합니다.

솔루션을 마우스 오른쪽 버튼으로 클릭하고 NuGet 패키지 관리를 선택합니다.

수정 사항 구현

  1. 단위 테스트를 받는 것이 도움이 됩니다.test@gmail, com.와 같은 잘못된 이메일 주소로 엑셀 파일을 만듭니다. (점 대신 쉼표를 참고하세요.)
  2. 여는 스트림과 스프레드시트 문서에 대한 호출을 확인합니다.열기는 읽기 및 쓰기를 허용합니다.
  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);

이 솔루션의 좋은 점 중 하나는 임시 "고정" 버전의 파일을 만들 필요가 없고 코드도 훨씬 적다는 것입니다.

유감스럽게도 당신이 zip으로 파일을 열고 고장난 하이퍼링크를 교체해야 하는 해결책은 저에게 도움이 되지 않을 것입니다.

저는 당신의 목표 프레임워크가 4.0일 때 당신이 유일하게 설치했더라도 그것이 어떻게 잘 작동하는지 궁금합니다.Net Framework 4.7.2 버전. 안에 개인 정적 가 있다는 것을 알게 .System.UriParserURI의 RFC 사양 버전을 선택합니다.따라서 의 .net 4.0 이하 버전에 대해 설정되어 있으므로 V2로 설정할 수 있습니다.넷 프레임워크.그것이 유일한 문제입니다.private static readonly.

아마도 누군가는 전체 애플리케이션에 대해 전체적으로 설정하기를 원할 것입니다.하지만 나는 썼습니다.UriQuirksVersionPatcher이 버전을 업데이트하고 폐기 방법으로 복원합니다.그것은 분명히 스레드 세이프는 아니지만 제 목적에 적합합니다.

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))
    {
       //.....
    }
}

추신. 나중에 저는 누군가 이미 이 패처를 구현했다는 것을 발견했습니다. https://github.com/google/google-api-dotnet-client/blob/master/Src/Support/Google.Apis.Core/Util/UriPatcher.cs

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();
}

언급URL : https://stackoverflow.com/questions/29970814/openxml-excel-throw-error-in-any-word-after-mail-address

반응형