Bind Custom XML to content controls

Hello,

I can’t see an example in Aspose.Words Help to add custom XML to document and bind it to the content controls.

I have a document template which includes some building blocks in glossary. The build block contains some content controls. I want to add custom XML to document, insert building blocks and bind the content controls to tags in custom XML. Could you give me a reference for my situation?

I appreciate your helps.

Vu

Hello

Thanks for your request. Using Aspose.Words you can create new building blocks and insert them into a glossary document. You can modify or delete existing building blocks. You can copy or move building blocks between documents. You can insert content of a building block into a document.
https://reference.aspose.com/words/net/aspose.words.buildingblocks/
Regarding Custom XML, you can try using CustomXmlMarkup to achieve what you need. Please see the following link for more information:
https://reference.aspose.com/words/net/aspose.words.markup/
In additional, Aspose.Words now provides the CustomXmlMarkup class that represents custom XML in Word documents. At the moment custom XML is preserved only during DOCX open/save. Support for custom XML in DOC, WordML will be available in the next versions.
Best regards,

Thanks Andrey for your quick response.

I can use Aspose to insert predefined building block into document. As I mention, the building block has some content controls which are identified by title. Does Aspose.Words have a function to get a content control by its title?

I have a XML contains the data for Custom XML. Do you have an example code to add the XML to a document as Custom XML? I also want to bind a node in document, in this case it is a content control, to a node in Custom XML. Could you give me how to do this with Aspose.Words?

I appreciate for your helps.

Hi
Thanks for your request.

  1. Unfortunately, the current version of Aspose.Words does not allow identifying SDTs (Content Controls) by title. We will add this feature in one of the nearest versions. Your request has been linked to the appropriate issue. You will be notified as soon as it is resolved.
  2. I suppose you are talking about CustomXml properties:
    https://reference.aspose.com/words/net/aspose.words.markup/customxmlpropertycollection/
    i.e. you would like to bind Content Controls to the values stored in CustomXml properties. Unfortunately, there is no way to achieve this using the current version of Aspose.Words.
    Maybe, in your case, if you need to fill the document with data from XML, you can just using Mail Merge feature:
    https://docs.aspose.com/words/java/mail-merge-with-xml-data-source/
    Best regards,

Thanks Alexey.

I try to add a CustomXmlMarkup to document, but I catch an exception “Cannot insert a node of this type at this location.”. My code is:

Document doc = new Document("template.dotx");

CustomXmlMarkup customXML = new CustomXmlMarkup(doc, MarkupLevel.Inline);
doc.AppendChild(customXML);

Do you know what my mistakes here?

Thanks.

Hi there,
Thanks for your inquiry.
The reason this is causing an exception is because CustomXmlMarkup nodes can’t appear as a child of a Document in the Aspose.Words DOM. In Aspose.Words you can insert CustomXmlMarkup nodes as children only of Body, Table, Row, Paragraph or of other custom markup nodes (CustomXMLMarkup, StructuredDocumentTag).
Please see this page here for further details.
Thanks,

Thanks.

According to your reply, I change code as below, but still have the same exception message. Do you have a sample code to add a custom XML to document?

Document doc = new Document("template.dotx");

CustomXmlMarkup customXML = new CustomXmlMarkup(doc, MarkupLevel.Inline);
doc.Sections[0].Body.AppendChild(customXML);

Hi there,
Thanks for this additional information.
Please change MarkupLevel.Inline to MarkupLevel.Block for markup nodes that are to be inserted as children of the body (amongst paragraph and table nodes).
I will be sure to include some examples in the API for these members shortly.
Thanks,

I’m thinking a solution to adapt current Aspose.Words version to our exist templates. In our building blocks of templates, we use content controls instead of mergefields and we don’t want to create other versions for our templates.

Whenever insert a building block to document, I intend to replace mergefields for each content controls. After that, I use Document.MailMerge.Execute method to bind data. Do you think if possible? Is it weird?

In Aspose.Words document, I only see the code to insert mergefield by DocumentBuilder. How to insert to Document without builder?

Hi there,
Thanks for this additional information.
What you are looking to do sounds very useful and currently the way you are attempting to do it sounds like the best method to go about it. In the future it would be good if there was the ability to merge data straight into content controls. I have logged this request, we will inform you of any developments.
In the mean time you can replace content controls with merge fields using DocumentBuilder, are you running into trouble while doing this?
Thanks,

Thanks, Adam.

I’m working directly with Document instead of DocumentBuilder. I don’t know how to insert a merge field to document without using DocumentBuilder. Could you give me an idea for this?

Hi
Thanks for your request. Unfortunately, there is no way to create field without using DocumentBuilder. Field in MS Word document is not a single node, it consist of few nodes. Here is structure of field in MS Word:
[FieldStart][one or few Run nodes(field code)][FieldSeparator][one or few Run nodes(field value)][FieldEnd].
So in case of creating a field from scratch you would need to create at least five nodes. That is why creating of fields is wrapped into DocumentBuilder.InsertField method that creates all required nodes for you.
Best regards,

Hi Cameron,
Thanks for your inquiry.
As Alexey has stated you need to use DocumentBuilder to insert fields. You can however create a quick wrapper class which will allow you to insert fields more easily when working with the DOM. Please see the code below.

MergeField mergeField1 = new MergeField(doc.FirstSection.Body.FirstParagraph, "MyField");
MergeField mergeField2 = new MergeField(doc.FirstSection.Body.Paragraphs[4].LastChild, "MyField2", "\\* MERGEFORMAT");
string result = mergeField1.Result;
mergeField2.Update();
/// 
/// Facade class for a merge field for use directly with the DOM.
/// 
public class MergeField
{
    private Field mField;
    public MergeField(Node insertNode, string fieldName)
    : this(insertNode, fieldName, null)
    {
    }
    public MergeField(Node insertNode, string fieldName, string switches)
    : this(insertNode, fieldName, switches, null)
    {
    }
    public MergeField(Node insertNode, string fieldName, string switches, string fieldResult)
    {
        DocumentBuilder builder = new DocumentBuilder((Document)insertNode.Document);
        builder.MoveTo(insertNode);
        string code = string.Format("{0} {1} {2}", "MERGEFIELD", fieldName, switches);
        if (fieldResult == null)
            mField = builder.InsertField(code);
        else
            mField = builder.InsertField(code, fieldResult);
    }
    /// 
    /// Field result
    /// 
    public string Result
    {
        get { return mField.Result; }
        set { mField.Result = value; }
    }
    /// 
    /// The fieldtype of the field.
    /// 
    public FieldType Type
    {
        get { return mField.Type; }
    }
    /// 
    /// Peforms field update on the field.
    /// 
    public void Update()
    {
        mField.Update();
    }
    /// 
    /// Gets the field code of the merge field.
    /// 
    /// 
    public string GetFieldCode()
    {
        return mField.GetFieldCode();
    }
    /// 
    /// Removes the field from the document.
    /// 
    public void Remove()
    {
        mField.Remove();
    }
}

Thanks,

I can insert merge field to document by your code. Thanks, Adam, Alexey.

Now I’m facing with copying format from content control to merge field. For example:

I have building block like this: This is a content control.
When I replace content control by merge field, it is always “This is a <>.” without bold.

How do I copy the format of content control to merge field?

Hi there,
Thanks for your inquiry.
I have added an extra method to the facade class which will allow you to copy the formatting from the content in the control to the merge field. You can use it like this:

Document doc = new Document("Document.docx");
StructuredDocumentTag sdt = (StructuredDocumentTag)doc.GetChild(NodeType.StructuredDocumentTag, 0, true);
// Insert a new paragraph in place of the content control.
Paragraph para = new Paragraph(doc);
sdt.ParentNode.InsertBefore(para, sdt);
// Use the MERGEFORMAT switch to ensure formatting is copied over during mail merge.
MergeField mergeField = new MergeField(para, "MyField", "\\* MERGEFORMAT");
mergeField.CopyFormattingToResult(sdt.GetChildNodes(NodeType.Run, true).ToArray());
sdt.Remove();
doc.MailMerge.Execute(new string[] { "MyField" }, new string[] { "Value" });

and you can add this method to the MergeField class:

/// 
/// Moves each of the nodes in the list to the field result of the field.
/// Use in conjuction with the MERGEFORMAT switch to retain formatting of the selected nodes
/// during mail merge.
/// 
public void CopyFormattingToResult(Node[] nodeList)
{
    // Remove all existing nodes from the field result 
    Result = String.Empty;
    foreach (Node node in nodeList)
    {
        // Only move inline nodes.
        if (node.ParentNode.NodeType == NodeType.Paragraph)
            mField.End.ParentParagraph.InsertBefore(node.Clone(false), mField.End);
    }
}

Thanks,

Thanks, Adam.

I got an exception “Cannot insert a node of this type at this location.” at
sdt.ParentNode.InsertBefore(para, sdt)

Could you advice this issue?

Hi there,
Thanks for this additional information.
The code uses the first content control in the document as an example which is retrieved using the GetChild method. You will need to pass the appropriate content control to the method which should be block level (child of a body). Perhaps in your document the first control retrieved is inline.
If you are still having trouble could you please attach your template and any extra code you are using here and I will take a look into it for you.
Thanks,

Yes, seems that the control is inline.

Here is my code:

Document doc = new Document("template.dotx");
GlossaryDocument glossary = doc.GlossaryDocument;

// Create NodeImporter.
NodeImporter importer = new NodeImporter(glossary, doc, ImportFormatMode.UseDestinationStyles);

BuildingBlock blockEA021 = glossary.GetBuildingBlock(BuildingBlockGallery.Custom1, "1033", "EA021");

foreach (Paragraph param in blockEA021.Sections[0].Body.Paragraphs)
{
    doc.FirstSection.Body.AppendChild(importer.ImportNode(param, true));
}

// Try to copy a content control format to a merge field
StructuredDocumentTag sdt = (StructuredDocumentTag)doc.GetChild(NodeType.StructuredDocumentTag, 0, true);

// Insert a new paragraph in place of the content control.

Paragraph para = new Paragraph(doc);

sdt.ParentNode.InsertBefore(para, sdt);

// Use the MERGEFORMAT switch to ensure formatting is copied over during mail merge.
MergeField mergeField = new MergeField(para, "MyField", "\* MERGEFORMAT");
mergeField.CopyFormattingToResult(sdt.GetChildNodes(NodeType.Run, true).ToArray());

doc.Save(outputPath);

I attach the template as well. This is a dotx file but the upload tool in forum prevents the dotx, so I change extension to docx.

Thanks in advance.

Hi there,
Thanks for attaching your documents here.
Yes that is the problem indeed. I will provide some revised code shortly, first I just need to clarify something after inspecting your template.
Do you want the merge field to replace the content control (and the content control removed) or do you want the merge field to be inside the content control and the content control remain?
Thanks,

Hi Adam,

Thanks for your supports so far.

The solution is to replace content controls by merge field maybe a bad way. The requirement here is to use our current template which have building blocks included. The building blocks use content controls.

Now I think there may be an easier way which I don’t need to use merge field. I will replace the text of content controls by the real data. I try this and it seems work for me. I include the source code below.

We have some patterns. They are EA002, EA008, EA020 you can see in our building blocks. Now I’m facing with 2 other issues.

  1. How to deal with image content controls?
  2. After insert to document and replace values, the text I replace still appear as content controls, not normal text.

Do you have any ideas for my issues?

Document doc = new Document("template.dotx");
GlossaryDocument glossary = doc.GlossaryDocument;
NodeImporter importer = new NodeImporter(glossary, doc, ImportFormatMode.UseDestinationStyles);
BuildingBlock blockEA021 = glossary.GetBuildingBlock(BuildingBlockGallery.Custom1, "1033", "EA021");
blockEA021.Range.Replace("Select", "Click ", false, false);
blockEA021.Range.Replace("ABC123", "San Francisco ", false, false);
blockEA021.Range.Replace("list box", " drop down list", false, false);
foreach (Node node in blockEA021.FirstSection.Body.ChildNodes)
{
    doc.FirstSection.Body.AppendChild(importer.ImportNode(node, true));
}