CustomXmlParts.cs
//
// This code is part of Document Solutions for Word demos.
// Copyright (c) MESCIUS inc. All rights reserved.
//
using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using GrapeCity.Documents.Word;

namespace DsWordWeb.Demos
{
    // This example demonstrates how to create a document with multiple ContentControls,
    // whose values are mapped to user-created CustomXmlPart file, standard XML file
    // which defines various elements and attributes.
    // This document also contain a header and a footer with elements that are also mapped to
    // proper xml elements.
    public class CustomXmlParts
    {
        public GcWordDocument CreateDocx()
        {
            var doc = new GcWordDocument();

            // Create custom xml part and keep it in the document:
            CreateOrderCustomXmlFromString(doc);

            // Set custom xml document element value:
            SetDeliveryOption(doc, true);

            // Compose document with Case properties bound to proper CustomControls:

            // Create and map To text field:
            CreateOficialBody(doc);

            // Create and map Appeal checkbox and AppealDeadline Text control:
            CreateDeliveryCheckbox(doc);

            // Create Header and map Department xml element:
            CreateHeaderWithMappedDepartment(doc);

            // Create Header and map document built-in Author xml element:
            CreateFooterWithMappedAuthor(doc);

            // Done:
            return doc;
        }

        // Retrieve a CustomXmlPart from a document by name:
        private static CustomXmlPart GetCustomXmlPartByName(GcWordDocument doc, string name)
        {
            foreach (var xmlPart in doc.CustomXmlParts)
            {
                if (string.Compare(xmlPart.XmlDocument.DocumentElement.Name, name, true) == 0)
                    return xmlPart;
            }
            throw new ArgumentException(string.Format("Could not find custom xml part {0}", name));
        }


        // Find 'Order' CustomXmlPart and set its DeliveryAppeal xml element to value provided by method parameter:
        private static void SetDeliveryOption(GcWordDocument doc, bool deliveryRequired)
        {
            var xmlPart = GetCustomXmlPartByName(doc, "Order");

            var node = xmlPart.XmlDocument.SelectSingleNode(@"//*[local-name()='Delivery']");
            node.InnerText = deliveryRequired ? "true" : "false";
        }

        // Compose half of document body, map "To" text custom control to proper CustomXml field:
        private static void CreateOficialBody(GcWordDocument doc)
        {
            var p = doc.Body.Paragraphs.Add("Dear ");
            var to = p.GetRange().ContentControls.Add(ContentControlType.Text, false);
            to.XmlMapping.SetMapping(@"//mescius:To", @"xmlns:mescius='http://developer.mescius.com'");

            p.GetRange().Runs.First.Font.Size = 10;
            to.Font.Size = 10;
            p.GetRange().Runs.Add(",");
            doc.Body.Paragraphs.Add();

            p = doc.Body.Paragraphs.Add(
                "The first shipment of equipment from AMA Ltd has arrived. " +
                "We are delighted with every piece. Therefore, we decided to make " +
                "our initial purchase larger than anticipated. I am attaching our " +
                "purchase order No. 8393 for additional goods. Since you already have " +
                "a copy of our Procurement Guidelines, I shall not attach them to " +
                "this order. Current Shipping date is ");

            // Add shipping Date control, bound to proper section of Order CustomXmlPart:
            var dateControl = p.GetRange().ContentControls.Add(ContentControlType.Date, false);
            dateControl.DateFormat = "dd.MM.yyyy";
            //here we demonstrate how to map CustomControl with namespaces usage, it allows us write
            //clean and straight XmlPath. Its the right way of XMlMapping
            dateControl.XmlMapping.SetMapping(@"//mescius:Delivery/@DeliveryDate",
                @"xmlns:mescius='http://developer.mescius.com'");
            p.GetRange().Paragraphs.Add(
                "If you want to change it, select checkbox below and change Date, " +
                "then send this letter back to me");
            dateControl.Font.Italic = true;

            p.GetRange().Runs.First.Font.Size = 12;
            p.GetRange().Runs.First.Font.Italic = true;
        }

        // Create footer and map Author built-in property to Text content control:
        private static void CreateFooterWithMappedAuthor(GcWordDocument doc)
        {
            var footer = doc.Body.Sections.First.Footers[HeaderFooterType.Primary];
            var p = footer.Body.Paragraphs.Add("Sincerely yours, ");

            if (p.Document.Settings.BuiltinProperties.Author == null)
                p.Document.Settings.BuiltinProperties.Author = "Nancy Davolio";

            // Create Text ContentControl and bind its value to builtin Author property:
            var authorTextControl = p.GetRange().ContentControls.Add(ContentControlType.Text, false);

            // Use specialized SetMapping overload for document BuiltInProperties.
            // here we bind (map) ContentControl to builtin Author property:
            authorTextControl.XmlMapping.SetMapping(() => p.Document.Settings.BuiltinProperties.Author);
        }

        // Create header with Text ContentControl mapped to 'Department' node of 'Order' CustomXml:
        private static void CreateHeaderWithMappedDepartment(GcWordDocument doc)
        {
            // Create Header and fill it with data:
            var header = doc.Body.Sections.First.Headers[HeaderFooterType.Primary];
            var p = header.Body.Paragraphs.Add();
            var departmentTextControl = p.GetRange().ContentControls.Add(ContentControlType.Text, false);

            // Get our Case CustomXmlPart:
            var xmlPart = GetCustomXmlPartByName(doc, "Order");

            var departmentNode = xmlPart.XmlDocument.SelectSingleNode(@"//*[local-name()='Department']");
            // Mapping directly to XmlNode:
            departmentTextControl.XmlMapping.SetMapping(departmentNode);
        }

        // Create Delivery part, Checkbox custom control bound to Delivery node and Date custom control
        // bound to details of (possible) Delivery date.:
        private static void CreateDeliveryCheckbox(GcWordDocument doc)
        {
            var p = doc.Body.Paragraphs.Add();

            // Create checkbox and map it to Shipping node of 'Case' CustomXmlPart:
            var checkBox = p.GetRange().ContentControls.Add(ContentControlType.CheckBox, false);

            // Here we demonstrate how to map CustomControl ignoring namespaces used in the xml.
            // This way should be avoided as much as possible as pretty inefficient:
            checkBox.XmlMapping.SetMapping(@"//*[local-name()='Delivery']");

            p.GetRange().Runs.Add("Delivery should be done before ");

            // Add shipping Date control, bound to proper section of Order CustomXmlPart:
            var dateControl = p.GetRange().ContentControls.Add(ContentControlType.Date, false);
            dateControl.DateFormat = "dd.MM.yyyy";
            // Here we demonstrate how to map CustomControl with namespaces usage, it allows us write
            // clean and straight XmlPath. Its the right way of XMlMapping:
            dateControl.XmlMapping.SetMapping(@"//mescius:Delivery/@DeliveryDate",
                @"xmlns:mescius='http://developer.mescius.com'");
        }

        // Create XML from a string (see alternative method below):
        private static void CreateOrderCustomXmlFromString(GcWordDocument doc)
        {
            var sb = new System.Text.StringBuilder(201);
            const string ns = "'http://developer.mescius.com'";

            sb.AppendLine(@"<Order xmlns=" + ns + ">");
            sb.AppendLine(@"  <To>Mark Donahue</To>");
            sb.AppendLine(@"  <Department>Shipping department</Department>");
            sb.AppendLine(@"  <Delivery DeliveryDate=""12.04.2019"">true</Delivery>");
            sb.AppendLine(@"</Order>");

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(sb.ToString());
            CustomXmlPart xmlPart = doc.CustomXmlParts.Add(xml, Guid.NewGuid().ToString(), new string[] { ns }, "application/xml");
        }

        // Create XML by adding individual nodes one by one
        // (this method is not used in this sample, the previous
        // alternative is used instead):
        private static void CreateOrderCustomXml(GcWordDocument doc)
        {
            // add a custom xml part with custom settings
            const string ns = "http://developer.mescius.com";
            XmlDocument xml = new XmlDocument();

            xml.AppendChild(xml.CreateXmlDeclaration("1.0", "utf-8", null));
            XmlElement root = xml.CreateElement("Order", ns);
            xml.AppendChild(root);

            var to = root.AppendChild(xml.CreateElement("To", ns));
            to.InnerText = "Mark Donahue";

            var dep = root.AppendChild(xml.CreateElement("Department", ns));
            dep.InnerText = "Shipping department";

            XmlElement child = xml.CreateElement("Delivery", ns);
            child.InnerText = "true";
            child.SetAttribute("DeliveryDate", "12.04.2019");

            root.AppendChild(child);

            CustomXmlPart xmlPart = doc.CustomXmlParts.Add(xml, Guid.NewGuid().ToString(), new string[] { ns }, "application/xml");

            // Sad story: we cannot apply schemas to our custom xml parts.
            // Unlike in Office excel (where a schema can be serialized inside a document),
            // it seems that Word can work with schema URIs only so we cannot place an xsd inside the document.
            // This means that we cannot implement native xml-mapping restrictiong and verifying like
            // xmlPart.Schemas.Add(CreateCelebrationScheme());
        }
    }
}