This project has moved. For the latest updates, please go here.

.docm support possible?

Feb 19, 2015 at 11:32 AM
Edited Feb 24, 2015 at 2:33 PM
Hi,
just tried to open an empty .docm document -- and DocX throws an System.InvalidOperationException: Sequence contains no elements. The exception is thrown in DocX.cs in the LINQ statement below:
        internal static DocX PostLoad(ref Package package)
        {
            [...]

            #region MainDocumentPart
            document.mainPart = package.GetParts().Where
            (
                     p => p.ContentType.Equals(HelperFunctions.DOCUMENT_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase) ||
                     p.ContentType.Equals(HelperFunctions.TEMPLATE_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase)
            ).Single();
            [...]
I do not have much knowledge of different Word file formats -- I just did a quick diff between the xml files in .docx and .docm -- they differ more than I thought. So the big question is: Would it be possbile for DocX to also support .docm?

BTW: I just created a small unit test in DocXUnitTest.cs -- you might find it useful:
      [TestMethod]
      public void OpenDocmFile()
      {
          try
          {
              using (DocX document = DocX.Load(Path.Combine(_directoryWithFiles, "empty.docm")))
              {
              }
          }
          catch (Exception exc)
          {
              Assert.Fail("No exception expected.", exc);
          }
      }
To create empty.docm just create a new empty document in Word and save it as .docm instead of .docx.
Feb 24, 2015 at 2:32 PM
Took a look at it again and made the following modifications:

To HelperFunctions.cs:
public const string MACRO_DOCUMENTTYPE = "application/vnd.ms-word.document.macroEnabled.main+xml";
To DocX.cs, method PostLoad:
p => p.ContentType.Equals(HelperFunctions.DOCUMENT_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase) ||
p.ContentType.Equals(HelperFunctions.TEMPLATE_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase) ||
p.ContentType.Equals(HelperFunctions.MACRO_DOCUMENTTYPE, StringComparison.CurrentCultureIgnoreCase)
So DocX now loads the docm file but changes to the document are not visible in the document saved. I.e. Properties are not set, paragraphs not added etc. Anybody a clue why?
Developer
Feb 24, 2015 at 2:34 PM
I guess you saw this (http://docx.codeplex.com/discussions/531149)? Maybe it will give you some hints as it seems similar topic. Hopefully you can give us a patch that will add this functionality :-)
Feb 24, 2015 at 2:46 PM
Ok, I'll give 1.0.1.16 a try.
Feb 24, 2015 at 2:57 PM
Just downloaded the most actual source code -- but there're no changes except those I made myself. So as .docm is a "final format" like .docx and not a template I guess this is another issue (i.e. something like wordDoc.ApplyTemplate(@"C:\Template.dotx"); doesn't make sense).

Further pointers?
Developer
Feb 24, 2015 at 4:38 PM
I wasn't referring to final solution but rather to an approach of stims. You're right it doesn't work and you're our only hope :)
Feb 26, 2015 at 6:38 PM
Edited Feb 26, 2015 at 6:40 PM
I did some testing. Think it was a bug in my application and not in DocX. Here's a unit test showing that setting a core property works:
      [TestMethod]
      public void ModifyDocmAttribute()
      {
          var fileName = "empty.docm";
          var targetFile = Path.Combine(Path.GetTempPath(), fileName);
          File.Copy(Path.Combine(_directoryWithFiles, fileName), targetFile);
          var creatorAttribute = "dc:creator";
          var targetValue = "Joe Doe";

          using (DocX document = DocX.Load(targetFile))
          {
              var value = document.CoreProperties[creatorAttribute];

              Assert.AreNotEqual(targetValue, value);

              document.AddCoreProperty(creatorAttribute, targetValue);

              document.Save();
          }

          using (DocX document = DocX.Load(targetFile))
          {
              var value = document.CoreProperties[creatorAttribute];

              Assert.AreEqual(targetValue, value);
          }

          File.Delete(targetFile);
      }
Sadly I can't upload the empty.docm file...

To summarize: Looks the changes above did the trick -- i.e. that's all to do for .docm support.

What I didn't explicitly check is that any files are copied from the original source file by DocX (i.e. macro files etc.) to the target file. But as the macro was called when opening the .docm file in Word it obviously was. Perhaps you can confirm this -- ideally with a reference to the unit test case?
May 7, 2015 at 7:49 PM
I followed the instructions and did a red-green-refactoring. I had some issues in NCrunch that the references to the DLL were not updated in all places, but now it works.

The only thing I would change is the unit test: remove the try-catch-block. Unit tests that throw an exception will automatically fail and they fail with a better error message.

Getting access to TFS:
You have to register at the project to become a member of the development team. Then, the settings are
TFS server: tfs.codeplex.com
Path: tfs
Protocol: HTTPS
Port: 443
User name: snd\username_cf (replace username)
Jun 3, 2015 at 6:49 AM
Try to add the properties about marco to Class DocX.
        internal PackagePart vbaDataPart;
        internal PackagePart vbaProjectPart;

        internal XDocument vbaData;
        internal XDocument vbaProject;
And then, you should set the value when loading.
                        case "application/vnd.ms-word.vbaData+xml":
                            vbaDataPart = packagePart;
                            using (TextReader tr = new StreamReader(vbaDataPart.GetStream()))
                                vbaData = XDocument.Load(tr);
                            break;

                        case "application/vnd.ms-office.vbaProject":
                            vbaProjectPart = packagePart;
                            using (TextReader tr = new StreamReader(vbaProjectPart.GetStream()))
                                vbaProject = XDocument.Load(tr);
                            break;
Finally, you should write the following properties when saving.
                if (vbaDataPart != null)
                {
                    using (TextWriter tw = new StreamWriter(vbaDataPart.GetStream(FileMode.Create, FileAccess.Write)))
                        vbaData.Save(tw, SaveOptions.None);
                }

                if (vbaProjectPart != null)
                {
                    using (TextWriter tw = new StreamWriter(vbaProjectPart.GetStream(FileMode.Create, FileAccess.Write)))
                        vbaProject.Save(tw, SaveOptions.None);
                }