1

The following is the code I'm using to create a new word document from an existing document. What does work is that it successfully reads the template document (templateName) and it is able to create the 'customXML' string from the model class. But what is not working is the customXML information (address, city, state, zip,...) is not replacing the empty tags.

I pulled this code from an older .NET web application and I'm using it in MVC 4.5 with OpenXml 2.5. The Older version of the code works but not in the MVC version.

    public void CreateDocument(Customer customer, string templateName, string documentPath)
    {
        // Need to create an in memory resizable byte array for loading the county template

        byte[] byteArray = File.ReadAllBytes(templateName);
        using (MemoryStream mem = new MemoryStream())
        {
            mem.Write(byteArray, 0, (int)byteArray.Length);

            // Open the in memory Open XML document for processing 
            //using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(templateName, true))
            using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(mem, true))
            {

                // Build XML file from the Data Class that will be populated from data entered
                // into the Add Customer screen.  
                // THIS CODE WORKS - A VALID XML STRING IS CREATED
                string customXML = BuildCustomXmlPartFromDataClass(customer);

                // Replace Custom XML portion of Open XML Package document
                // THE mainPart.CustomXmlParts IS NOT NULL
                MainDocumentPart mainPart = wordDoc.MainDocumentPart;
                if (mainPart.CustomXmlParts != null)
                {
                    mainPart.DeleteParts<CustomXmlPart>(mainPart.CustomXmlParts);
                }
                CustomXmlPart customXmlPart = mainPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);
                using (StreamWriter ts = new StreamWriter(customXmlPart.GetStream())) ts.Write(customXML);
            }

            // Save the in memory byte array to disk using a FileStream
            using (FileStream fileStream = new FileStream(documentPath, System.IO.FileMode.Create))
            {
                mem.WriteTo(fileStream);
            }
            // NEW DOCUMENT IS CREATED BUT IT DOES NOT CONTAIN ANY OF THE INFORMATION FROM THE MODEL CLASS
        }

    }

    protected string BuildCustomXmlPartFromDataClass(Customer customer)
    {
        // Serialize the Data Class to XML
        System.Xml.XmlDocument doc = new XmlDocument();
        System.Xml.Serialization.XmlSerializer serializer = 
            new System.Xml.Serialization.XmlSerializercustomer.GetType());
        System.IO.MemoryStream stream = new System.IO.MemoryStream();
        try
        {
            serializer.Serialize(stream, customer);
            stream.Position = 0;
            doc.Load(stream);
            return doc.InnerXml;
        }
        catch
        {
            throw;
        }
        finally
        {
            stream.Close();
            stream.Dispose();
        }

    }

There are no errors and a new document is created but it does not contain any information from the Model class (customer). I have seen similar posts but the solution(s) did not work.

Joseph Sage
  • 11
  • 1
  • 2

1 Answers1

1

First, we probably need some more information:

  1. Is the XML being generated the same as what is already in the document?
    (Does it have the same namespace, format, etc?)
  2. Is there a possibility that the content controls in the document
    simply aren't mapped?
  3. Can you verify the XML is being written to the document?
    (It can be present in the document, but not show because it's not mapped, or the mapping is broken because the XML is different.)

You can inspect the XML stored in the document in a variety of ways:

  1. Open XML SDK Productivity Tool
  2. Open XML Package Editor for Visual Studio
  3. Greg Maxey's Content Control Tools (VBA .dotm)
  4. Word Content Control Toolkit

I think rather than DeleteParts<CustomXmlPart>(mainPart.CustomXmlParts) you should selectively replace the part you are looking for. You can (should) use the xmlns of the root element to identify parts (or you can test for root element name if your part doesn't have an xmlns and you can't add one either).

This code demonstrates how to build the XML with a root namespace (which means all children need to also have that namespace). You'll have to research how to specify namespaces via attributes (or implement IXmlSerializable):

XDocument GetXDocument()
{
    var xmlns = (XNamespace)"http://schemas.tempuri.org/product/v1/wordMetadata";

    var childElement =
        new XElement(xmlns + "childElement",
            this.ChildCollection.Select(x => x.GetXElement(xmlns, "grandChildElement")));

    var rValue = new XDocument(
        new XElement(xmlns + "root",
            childElement
        ));

    return rValue;
}

public XElement GetXElement(XNamespace xmlns, string elementName)
{
    return new XElement(xmlns + elementName);
}

The code below will replace the XML part in the document that has the same namespace as the incoming XDocument.

public static class WriteWord
{
    public static MemoryStream BuildFile(string templatePath, XDocument xmlData)
    {
        MemoryStream rValue = null;
        byte[] fileBytes;

        fileBytes = File.ReadAllBytes(templatePath);
        rValue = BuildFile(fileBytes, xmlData);

        return rValue;
    }

    public static MemoryStream BuildFile(byte[] fileBytes, XDocument xmlData)
    {
        var rValue = new MemoryStream();

        var reader = xmlData.CreateReader();
        reader.MoveToContent();
        var xmlNamespace = reader.NamespaceURI; // "http://schemas.tempuri.org/product/v1/wordMetadata";

        rValue.Write(fileBytes, 0, fileBytes.Length);

        var openSettings = new OpenSettings()
        {
            AutoSave = true,
            //MarkupCompatibilityProcessSettings = 
            //    new MarkupCompatibilityProcessSettings(
            //        MarkupCompatibilityProcessMode.ProcessAllParts, 
            //        FileFormatVersions.Office2013)
        };
        using (WordprocessingDocument doc = WordprocessingDocument.Open(rValue, true, openSettings))
        {
            MainDocumentPart main = doc.MainDocumentPart;

            var mainPart = doc.MainDocumentPart;
            var xmlParts = mainPart.CustomXmlParts;
            var ourPart = (CustomXmlPart)null;

            foreach (var xmlPart in xmlParts)
            {
                var exists = false;
                using (XmlTextReader xReader = new XmlTextReader(xmlPart.GetStream(FileMode.Open, FileAccess.Read)))
                {
                    xReader.MoveToContent();
                    exists = xReader.NamespaceURI.Equals(xmlNamespace);
                }

                if (exists)
                {
                    ourPart = xmlPart;
                    break;
                }
            }

            using (var xmlMS = new MemoryStream())
            {
                xmlData.Save(xmlMS);
                xmlMS.Position = 0;
                ourPart.FeedData(xmlMS);
            }

        }

        rValue.Position = 0;


        return rValue;

    }
}
JoeBrockhaus
  • 2,745
  • 2
  • 40
  • 64