Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open XML SDK System.IO.IOException: "Entries cannot be opened multiple times in Update mode" during disposal #1801

Closed
Yaroslav-Andrieiev-Net opened this issue Oct 3, 2024 · 5 comments
Assignees

Comments

@Yaroslav-Andrieiev-Net
Copy link

Describe the bug

Hi everyone, we faced an issue after "DocumentFormat.OpenXml" migration from 2.20.0 to latest 3.1.0 (issue reproduces in versions 3.0.0 - 3.1.0).
It happens on disposal of WordprocessingDocument
Stacktrace:
- System.IO.IOException: Entries cannot be opened multiple times in Update mode. at System.IO.Compression.ZipArchiveEntry.OpenInUpdateMode() at System.IO.Packaging.ZipStreamManager.Open(ZipArchiveEntry zipArchiveEntry, FileAccess streamFileAccess) at System.IO.Packaging.ZipPackagePart.GetStreamCore(FileMode streamFileMode, FileAccess streamFileAccess) at DocumentFormat.OpenXml.Packaging.OpenXmlPart.LoadDomTree[T]() at DocumentFormat.OpenXml.Packaging.MainDocumentPart.get_PartRootElement() at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.SavePartContents(Boolean save) at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.Dispose(Boolean disposing) at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.Dispose() at Edocs.Infrastructure.Services.Conversion.OpenXMLCorrector.FixSharePointFile(MemoryStream stream, IFileLoggerInfo fileLoggerInfo, String extension)

Issue reproduces only with particular .docx file: File.docx

Code

public void FixSharePointFile(MemoryStream stream, IFileLoggerInfo fileLoggerInfo, string extension)
{
    WordprocessingDocument wordDoc = null;
    try
    {
        try
        {
            wordDoc = WordprocessingDocument.Open(stream, true);
        }
        catch (InvalidOperationException e)
        {
            Log.Error($"OpenXML file open exception. CaseFileId: {fileLoggerInfo.FileId}, file name: {fileLoggerInfo.FileName}", e);
            throw new BusinessException(LocalizedString.Get(Messages.OpenXMLFileOpenError));
        }

        var partsAndBindings = wordDoc.MainDocumentPart.GetXMLPartsAndBindings();
        var doc = wordDoc.MainDocumentPart.GetXmlDocByNamespace(EdocsConversionConstants.Myns);

        if (doc is { ChildNodes.Count: > 0 })
        {
            var namespaceManager = new XmlNamespaceManager(doc.NameTable);
            var xDoc = doc.ToXDocument();

            foreach (var part in partsAndBindings)
            {

                foreach (var binding in part.Item2)
                {
                    var xpath = binding.Attribute(EdocsConversionConstants.W + "xpath")?.Value;

                    var prefix = binding.Attribute(EdocsConversionConstants.W + "prefixMappings")?.Value;
                    this.PrefixResolver(prefix, namespaceManager);

                    if (xpath.IsNotEmpty())
                    {
                        var sdtPrCollection = binding.Parent?.Parent?.Descendants(EdocsConversionConstants.W + "sdtPr");
                        var rCollection = binding.Parent?.Parent?.Descendants(EdocsConversionConstants.W + "r");

                        if (sdtPrCollection.Any() && rCollection.Any())
                        {
                            var r = rCollection?.GetFirstAndRemoveOthers();

                            var sdtPr = sdtPrCollection?.First();

                            var rprCollection = sdtPr?.Descendants(EdocsConversionConstants.W + "rPr");

                            if (rprCollection.Any())
                            {
                                var rPr = rprCollection?.First();
                                var rPrRCollection = r?.Descendants(EdocsConversionConstants.W + "rPr");
                                if (rPrRCollection.Any())
                                {
                                    rPrRCollection.First()?.ReplaceWith(rPr);
                                }
                            }

                            var t = r?.Descendants(EdocsConversionConstants.W + "t")?.GetFirstAndRemoveOthers();

                            r?.Descendants(EdocsConversionConstants.W + "tab")?.GetFirstAndRemoveOthers()?.Remove();
                            r?.Descendants(EdocsConversionConstants.W + "br")?.GetFirstAndRemoveOthers()?.Remove();

                            if (t != null)
                            {
                                var v = xDoc?.XPathSelectElement(xpath, namespaceManager)?.Value;
                                if (v.IsEmpty())
                                {
                                    t.Remove();
                                }
                            }
                        }
                    }
                }

                part.Item1.PutXDocument();
            }
        }

        wordDoc?.Dispose(); // exception throwed here!

        stream?.Seek(0, SeekOrigin.Begin);
    }
    finally
    {
        wordDoc?.Dispose();
    }
}

Extenstion methods:

public static List<Tuple<OpenXmlPart, IEnumerable<XElement>>> GetXMLPartsAndBindings(this MainDocumentPart mainPart)
{
    var partsAndBindings = new List<Tuple<OpenXmlPart, IEnumerable<XElement>>>();

    foreach (var header in mainPart.HeaderParts)
    {
        var headerDoc = header.GetXDocument();

        var headerBinding = headerDoc?.Descendants(w + "dataBinding");
        if (headerBinding != null)
        {
            partsAndBindings.Add(new Tuple<OpenXmlPart, IEnumerable<XElement>>(header, headerBinding));
        }
    }

    foreach (var footer in mainPart.FooterParts)
    {
        var footerDoc = footer.GetXDocument();

        var footerBinding = footerDoc?.Descendants(w + "dataBinding");
        if (footerBinding != null)
        {
            partsAndBindings.Add(new Tuple<OpenXmlPart, IEnumerable<XElement>>(footer, footerBinding));
        }
    }
    var mainXml = mainPart.GetXDocument();
    var bindings = mainXml?.Descendants(w + "dataBinding");

    if (bindings != null)
    {
        partsAndBindings.Add(new Tuple<OpenXmlPart, IEnumerable<XElement>>(mainPart, bindings));
    }

    return partsAndBindings;
}
public static XmlDocument GetXmlDocByNamespace(this MainDocumentPart mainPart, string nameSpace)
{
    var doc = new XmlDocument();

    foreach (var part in mainPart.CustomXmlParts)
    {
        using (var stream = part.GetStream())
        {
            if (stream.Length == 0)
            {
                continue;
            }

            doc.Load(stream);

            if (doc?.DocumentElement?.NamespaceURI == nameSpace)
            {
                return doc;
            }
        }
    }

    return null;
}
public static XDocument ToXDocument(this XmlDocument document)
 {
     return document.ToXDocument(LoadOptions.None);
 }

 public static XDocument ToXDocument(this XmlDocument document, LoadOptions options)
 {
     using (var reader = new XmlNodeReader(document))
     {
         return XDocument.Load(reader, options);
     }
 }
 public static void PutXDocument(this OpenXmlPart part)
 {
     var xdoc = part.GetXDocument();
     xdoc.PutXDocument(part);
 }

 public static void PutXDocument(this XDocument xdoc, OpenXmlPart part)
 {
     if (xdoc != null)
     {
         using (var xw =
             XmlWriter.Create(part.GetStream
                 (FileMode.Create, FileAccess.Write)))
         {
             xdoc.Save(xw);
         }
     }
 }

Thanks in advance for any assistance!

@mkaszewiak
Copy link
Collaborator

mkaszewiak commented Jan 31, 2025

Hi @Yaroslav-Andrieiev-Net
The error you are seeing occurs when code tries to open stream when another stream is open. I recommend using using statement whenever possible to avoid handling all the stream. See this issue[(https://github.com//issues/909))]

To be able to troubleshoot your issue could you please provide the following so I can test it on my site and check for errors:
Whole class including extension method as there are variables in your code like:

this.PrefixResolver(prefix, namespaceManager);

that I am not able to resolve. Also please hardcode all the constants or provide constants class

EdocsConversionConstant

Possibly issue maybe somewhere in the extension methods as you are opening streams there but without working example I am not able to tell for sure.

Thanks

@Yaroslav-Andrieiev-Net
Copy link
Author

Yaroslav-Andrieiev-Net commented Feb 3, 2025

Hi @mkaszewiak
Here you go, the full classes:

public interface IFileLoggerInfo
{
    public int FileId { get; set; }

    public string FileName { get; set; }
}

public interface IOpenXMLCorrector : ISingletonDependency
{
    void FixSharePointFile(MemoryStream stream, IFileLoggerInfo printDocDocument, string extension);
}

public class OpenXMLCorrector : IOpenXMLCorrector
{
    private static readonly ILog Log = LogManager.GetLogger(CustomLoggerDefinition.Background);

    public void FixSharePointFile(MemoryStream stream, IFileLoggerInfo fileLoggerInfo, string extension)
    {
        WordprocessingDocument wordDoc = null;
        try
        {
            try
            {
                // TODO: Remove RelationshipErrorHandlerFactory after OpenXml SDK upgrade to 3.0.1+
                wordDoc = WordprocessingDocument.Open(stream, true, new OpenSettings
                {
                    RelationshipErrorHandlerFactory = RelationshipErrorHandler.CreateRewriterFactory((_, _, _) => "about:blank")
                });
            }
            catch (InvalidOperationException e)
            {
                Log.Error($"OpenXML file open exception. CaseFileId: {fileLoggerInfo.FileId}, file name: {fileLoggerInfo.FileName}", e);
                throw new BusinessException(LocalizedString.Get(Messages.OpenXMLFileOpenError));
            }

            var partsAndBindings = wordDoc.MainDocumentPart.GetXMLPartsAndBindings();
            var doc = wordDoc.MainDocumentPart.GetXmlDocByNamespace(EdocsConversionConstants.Myns);

            if (doc is { ChildNodes.Count: > 0 })
            {
                var namespaceManager = new XmlNamespaceManager(doc.NameTable);
                var xDoc = doc.ToXDocument();

                foreach (var part in partsAndBindings)
                {

                    foreach (var binding in part.Item2)
                    {
                        var xpath = binding.Attribute(EdocsConversionConstants.W + "xpath")?.Value;

                        var prefix = binding.Attribute(EdocsConversionConstants.W + "prefixMappings")?.Value;
                        this.PrefixResolver(prefix, namespaceManager);

                        if (xpath.IsNotEmpty())
                        {
                            var sdtPrCollection = binding.Parent?.Parent?.Descendants(EdocsConversionConstants.W + "sdtPr");
                            var rCollection = binding.Parent?.Parent?.Descendants(EdocsConversionConstants.W + "r");

                            if (sdtPrCollection.Any() && rCollection.Any())
                            {
                                var r = rCollection?.GetFirstAndRemoveOthers();

                                var sdtPr = sdtPrCollection?.First();

                                var rprCollection = sdtPr?.Descendants(EdocsConversionConstants.W + "rPr");

                                if (rprCollection.Any())
                                {
                                    var rPr = rprCollection?.First();
                                    var rPrRCollection = r?.Descendants(EdocsConversionConstants.W + "rPr");
                                    if (rPrRCollection.Any())
                                    {
                                        rPrRCollection.First()?.ReplaceWith(rPr);
                                    }
                                }

                                var t = r?.Descendants(EdocsConversionConstants.W + "t")?.GetFirstAndRemoveOthers();

                                r?.Descendants(EdocsConversionConstants.W + "tab")?.GetFirstAndRemoveOthers()?.Remove();
                                r?.Descendants(EdocsConversionConstants.W + "br")?.GetFirstAndRemoveOthers()?.Remove();

                                if (t != null)
                                {
                                    var v = xDoc?.XPathSelectElement(xpath, namespaceManager)?.Value;
                                    if (v.IsEmpty())
                                    {
                                        t.Remove();
                                    }
                                }
                            }
                        }
                    }

                    part.Item1.PutXDocument();
                }
            }

            wordDoc?.Dispose();

            stream?.Seek(0, SeekOrigin.Begin);
        }
        finally
        {
            wordDoc?.Dispose();
        }
    }

    private void PrefixResolver(string prefix, XmlNamespaceManager namespaceManager)
    {

        var xPrefixSepColl = prefix?.Trim()?.Split(
            new[]
            {
                "xmlns:"
            },
            StringSplitOptions.RemoveEmptyEntries);
        if (xPrefixSepColl == null || xPrefixSepColl.Length == 0)
        {
            return;
        }

        foreach (var xPrefixSep in xPrefixSepColl)
        {
            var s = new string(xPrefixSep.TakeWhile(x => x != '=').ToArray());
            if (!namespaceManager.HasNamespace(s))
            {
                var v = xPrefixSep.Trim().Replace(s + "=", string.Empty);
                if (v.StartsWith("\'"))
                {
                    v = v.Remove(0, 1);
                }
                if (v.EndsWith("\'"))
                {
                    v = v.Remove(v.Length - 1, 1);
                }
                namespaceManager.AddNamespace(s, v);
            }
        }
    }
}

Class with extenstion methods

public static class XmlDocumentExtensions
{
    private static readonly XNamespace w = @"http://schemas.openxmlformats.org/wordprocessingml/2006/main";

    public static string GetDataValue(this XElement dataElement, string existsValue)
    {
        var formatValue = dataElement.GetExementAttributeValue("dateFormat");
        var cultureValue = dataElement.GetExementAttributeValue("lid");

        var date = Convert.ToDateTime(existsValue);

        return date.ToString(formatValue, new CultureInfo(cultureValue));
    }

    public static XDocument ToXDocument(this XmlDocument document)
    {
        return document.ToXDocument(LoadOptions.None);
    }

    public static XElement GetFirstAndRemoveOthers(this IEnumerable<XElement> collection)
    {
        if (collection.Any())
        {
            var length = collection.Count();
            if (length > 1)
            {
                for (var i = length - 1; i > 0; i--)
                {
                    collection.ElementAt(i).Remove();
                }
            }

            return collection.FirstOrDefault();
        }

        return null;
    }

    public static XDocument ToXDocument(this XmlDocument document, LoadOptions options)
    {
        using (var reader = new XmlNodeReader(document))
        {
            return XDocument.Load(reader, options);
        }
    }

    public static XDocument GetXDocument(this OpenXmlPart part)
    {
        var xdoc = part.Annotation<XDocument>();
        if (xdoc != null)
            return xdoc;
        using (var sr = new StreamReader(part.GetStream()))
        using (var xr = XmlReader.Create(sr))
            xdoc = XDocument.Load(xr);
        part.AddAnnotation(xdoc);
        return xdoc;
    }

    public static void PutXDocument(this OpenXmlPart part)
    {
        var xdoc = part.GetXDocument();
        xdoc.PutXDocument(part);
    }

    public static void PutXDocument(this XDocument xdoc, OpenXmlPart part)
    {
        if (xdoc != null)
        {
            using (var xw =
                XmlWriter.Create(part.GetStream
                    (FileMode.Create, FileAccess.Write)))
            {
                xdoc.Save(xw);
            }
        }
    }

    public static XmlDocument GetXmlDocByNamespace(this MainDocumentPart mainPart, string nameSpace)
    {
        var doc = new XmlDocument();

        foreach (var part in mainPart.CustomXmlParts)
        {
            using (var stream = part.GetStream())
            {
                if (stream.Length == 0)
                {
                    continue;
                }

                doc.Load(stream);

                if (doc?.DocumentElement?.NamespaceURI == nameSpace)
                {
                    return doc;
                }
            }
        }

        return null;
    }

    private static string GetExementAttributeValue(this XElement dataElement, string query)
    {
        var cultureElement = dataElement.Descendants(w + query);

        if (cultureElement.Any())
        {
            return cultureElement.First().FirstAttribute.Value;
        }

        return null;
    }

    public static List<Tuple<OpenXmlPart, IEnumerable<XElement>>> GetXMLPartsAndBindings(this MainDocumentPart mainPart)
    {
        var partsAndBindings = new List<Tuple<OpenXmlPart, IEnumerable<XElement>>>();

        foreach (var header in mainPart.HeaderParts)
        {
            var headerDoc = header.GetXDocument();

            var headerBinding = headerDoc?.Descendants(w + "dataBinding");
            if (headerBinding != null)
            {
                partsAndBindings.Add(new Tuple<OpenXmlPart, IEnumerable<XElement>>(header, headerBinding));
            }
        }

        foreach (var footer in mainPart.FooterParts)
        {
            var footerDoc = footer.GetXDocument();

            var footerBinding = footerDoc?.Descendants(w + "dataBinding");
            if (footerBinding != null)
            {
                partsAndBindings.Add(new Tuple<OpenXmlPart, IEnumerable<XElement>>(footer, footerBinding));
            }
        }
        var mainXml = mainPart.GetXDocument();
        var bindings = mainXml?.Descendants(w + "dataBinding");

        if (bindings != null)
        {
            partsAndBindings.Add(new Tuple<OpenXmlPart, IEnumerable<XElement>>(mainPart, bindings));
        }

        return partsAndBindings;
    }
}

Constants:

public class EdocsConversionConstants
 {
     public static readonly XNamespace W = @"http://schemas.openxmlformats.org/wordprocessingml/2006/main";
     public const string Myns = @"http://schemas.microsoft.com/office/2006/metadata/properties";
 }

Let me know if something else needed, thanks in advance for help.

@mkaszewiak
Copy link
Collaborator

Hi @Yaroslav-Andrieiev-Net the issue is caused by this line

 using (var xw =XmlWriter.Create(part.GetStreamFileMode.Create, FileAccess.Write)))

in the PutXDocument method. You are trying to open stream that is already open with XmlWriter.
XmlWriter.Create(stream) the stream parameter here is the stream XmlWriter will write to but you not actually calling write method anyway so I just wonder if the below code will work for what You are trying to achieve?

public static void PutXDocument(this XDocument xdoc, OpenXmlPart part)
    {
        if (xdoc != null)
        {
            using (var xw = part.GetStream())
            {
                xdoc.Save(xw);

            }
        }
    }

As a side note while I was testing your issue I found out that below foreach loop is never entered so you may want to rethink the logic there if that is not expected behaviour

foreach (var binding in part.Item2)

Let me know how that works for you. Thanks

@mkaszewiak mkaszewiak self-assigned this Feb 5, 2025
@Yaroslav-Andrieiev-Net
Copy link
Author

Hi @mkaszewiak, It works! Thanks a lot for assistance

@mkaszewiak
Copy link
Collaborator

@Yaroslav-Andrieiev-Net You're very welcome! I'm glad to hear that it works for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants