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

ToFlatOpcString fails with generated document #380

Closed
daniel-white opened this issue Jan 16, 2018 · 20 comments
Closed

ToFlatOpcString fails with generated document #380

daniel-white opened this issue Jan 16, 2018 · 20 comments

Comments

@daniel-white
Copy link

daniel-white commented Jan 16, 2018

Description

ToFlatOpcString throws "Root element is missing." I'm trying to put this format into a VSTO add-in. When I enumerate wordprocessingDocument.Package.GetParts(), I get the relationship part back as an empty string (ContentType = "application/vnd.openxmlformats-package.relationships+xml"). Shouldn't AddMainDocumentPart take care of this under the covers?

Information

  • .NET Target: .NET Core 2.0, .NET Framework 4.7
  • DocumentFormat.OpenXml Version: 2.8.1

Repro

using System;
using System.IO;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (MemoryStream stream = new MemoryStream())
            using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document))
            {
                MainDocumentPart mainDocumentPart = wordprocessingDocument.AddMainDocumentPart();
                Body body = new Body(new Paragraph(new Run(new RunProperties(new Bold()),
                    new Text("Hello world this should be bold"))));
                mainDocumentPart.Document = new Document(body);

                wordprocessingDocument.Save();

                Console.WriteLine(wordprocessingDocument.ToFlatOpcDocument());
            }

        }
    }
}

Observed

Exception:

System.Xml.XmlException: Root element is missing.
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.ParseDocumentContent()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XmlReader.MoveToContent()
   at System.Xml.Linq.XElement.Load(XmlReader reader, LoadOptions options)
   at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.GetContentsAsXml(PackagePart part)
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at System.Xml.Linq.XContainer.AddContentSkipNotify(Object content)
   at System.Xml.Linq.XContainer.AddContentSkipNotify(Object content)
   at DocumentFormat.OpenXml.Packaging.OpenXmlPackage.ToFlatOpcDocument(XProcessingInstruction instruction)
   at ConsoleApp1.Program.Main(String[] args)

Expected

That ToFlatOpcString doesn't throw and I get the resulting XML string.

@daniel-white
Copy link
Author

daniel-white commented Jan 16, 2018

I can workaround it with

foreach (PackagePart packagePart in wordprocessingDocument.Package.GetParts())
{
    if (packagePart.ContentType == "application/vnd.openxmlformats-package.relationships+xml")
    {
        using (Stream packagePartStream = packagePart.GetStream())
        using (StreamWriter streamWriter = new StreamWriter(packagePartStream))
        {
            streamWriter.Write("<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"word/document.xml\"/></Relationships>");
        }
    }
}

but that is a dirty hack.

@daniel-white
Copy link
Author

daniel-white commented Jan 17, 2018

In my further digging there seems to be 1 or 2 problems.

  1. Save doesn't actually invoke a save of this part. I tried modifying to call Flush on _metroPackage, but i got another exception.
  2. InitPart does add the relationship to the _metroPackage, but it doesnt seem to flush out its content.

I'll keep digging but there seems to be a problem either with the SDK or the Package class.

@daniel-white
Copy link
Author

So System.IO.Packaging.Package will call a private FlushRelationships on a Flush call, but the OpenXmlPackage does not call Flush on Save.

@tomjebo tomjebo added the bug label Jan 17, 2018
@tomjebo
Copy link
Collaborator

tomjebo commented Jan 17, 2018

@daniel-white thanks for reporting this and providing the extra information. I'll take a look. We did just update some of the Save functionality but not sure if that's related. @twsouthwick may know of top of head. otherwise, I'll check into it.

@daniel-white
Copy link
Author

Calling Flush on the package fails with other exceptions at different times.

@twsouthwick
Copy link
Member

This seems similar to an open issue. I added a fix for last release to enable saving when auto-save is disabled, but this was on close. Being able to save while the stream is still open is unfortunately blocked in CoreFx. Please see #294 which is the tracking issue in this repository.

If you were to switch to v2.7.2, does it work? I want to make sure it is not a regression.

@twsouthwick
Copy link
Member

Wrong button...

@daniel-white
Copy link
Author

@twsouthwick not a regression with .net 4.7 or .net core 2.0 using v2.7.2. same exception.

@daniel-white
Copy link
Author

daniel-white commented Jan 17, 2018

would another "stupid workaround" be to close the WordprocessingDocument, rewind the memory stream and reopen?

yea that works since that gets to a Flush call on the Package, not ideal... but its a start

@twsouthwick
Copy link
Member

Yup. That's probably the best way to do it right now, although the stream itself will have to be recreated as it will be in a disposed state.

@daniel-white
Copy link
Author

It worked, so when I pass a stream off to Open, it will cause it to be disposed?

@twsouthwick
Copy link
Member

It'll be disposed after closing it (which forces the save/flush). Can you post what you get working for posterity? Hopefully we'll get the fixes needed in CoreFx so we can make this easier, but for now, having possible work arounds will be helpful

@daniel-white
Copy link
Author

daniel-white commented Jan 17, 2018

here's what i got working. im guessing a MemoryStream can be rewound since its just a wrapper for a byte[].

 using (MemoryStream stream = new MemoryStream())
            {
                using (WordprocessingDocument wordprocessingDocument =
                    WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document))
                {
                    MainDocumentPart mainDocumentPart = wordprocessingDocument.AddMainDocumentPart();

                    /*      foreach (PackagePart packagePart in wordprocessingDocument.Package.GetParts())
                          {
                              if (packagePart.ContentType == "application/vnd.openxmlformats-package.relationships+xml")
                              {
                                  using (Stream packagePartStream = packagePart.GetStream())
                                  using (StreamWriter streamWriter = new StreamWriter(packagePartStream))
                                  {
                                      streamWriter.Write("<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"><Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"word/document.xml\"/></Relationships>");
                                  }
          
                              }
                          } */


                    Body body = new Body(new Paragraph(new Run(new RunProperties(new Bold()),
                        new Text("Hello world this should be bold"))));
                    mainDocumentPart.Document = new Document(body);

                    wordprocessingDocument.Save();
                }

                stream.Seek(0, SeekOrigin.Begin);
                using (WordprocessingDocument doc = WordprocessingDocument.Open(stream, false))
                {
                    Console.WriteLine(doc.ToFlatOpcString());
                }
            }

@twsouthwick
Copy link
Member

twsouthwick commented Jan 17, 2018

It'll still throw "ObjectDisposedException" during some operations. If this does work, then maybe it doesn't get disposed during the package closing (can't remember off the top of my head)

@daniel-white
Copy link
Author

Fair enough.

On another note, the unit tests don't test this path at all, perhaps for this very reason.

What's troubling is that this is failing in .NET framework in the same way the .NET Core build does. adding a call to doc.Package.Flush fixes it on .NET framework. Are you guys going for parity here? Would a #if NETFRAMEWORK conditional inside of Save make sense to at least get the .net framework working?

@twsouthwick
Copy link
Member

We're going for behavior parity between .NET Core and .NET Framework. We could add a fix for .NET Framework only, though, and have a list of known issues for .NET Core....

@twsouthwick
Copy link
Member

As far as the tests are concerned, there are a lot of cases the tests don't cover unfortunately. It seems that as it was originally developed some integration tests were added for document conversion, but basic functionality don't have many unit tests. Feel free to add some, and mark as skipped with a link to the issues... building up tests is always a good thing!

daniel-white pushed a commit to daniel-white/Open-XML-SDK that referenced this issue Jan 17, 2018
@tomjebo
Copy link
Collaborator

tomjebo commented Jan 17, 2018

@daniel-white we do have FlatOpcTests.cs with ToFlatOpcString(). Can you add your test there instead?

@twsouthwick
Copy link
Member

I'm going to close this one as the issue is tracking in #294. Once that is fixed, we'll want to enable these tests.

@twsouthwick
Copy link
Member

@daniel-white I've opened a PR to enable saving with a flag on OpenXmlPackage.CanSave to indicate whether the platform supports fully saving. It will still not work on .NET Core, but will allow saving when possible. Please have a look at #468 and give any feedback you may have.

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

No branches or pull requests

3 participants