Aspose.Words

How-to: Replace Fields with Static Text

You can download the complete source code of the ReplaceFieldsWithStaticText sample here.

This technique refers to removing dynamic fields from a document which change the text they display when updated and transforming them into plain text that will remain as they are even when fields are updated.

This is often required when you wish to save your document as a static copy, for example for when sending as an attachment in an e-mail. The conversion of fields such as a DATE or TIME field to static text will enable them to display the same date as when you sent them. In some situations you may need to remove conditional IF fields from your document and replace them with the most recent text result instead. For example, converting the result of an IF field to static text so it will no longer dynamically change its value if the fields in the document are updated.

The Solution

The process of converting fields to static text involves extracting the field result (the most recently updated text stored in the field) and retaining this value while removing the field objects around it. This will result in what was a dynamic field to be static text instead.

For example, the diagram below shows how an “IF” field is stored in a document. The text is encompassed by the special field nodes FieldStart and FieldEnd. The FieldSeparator node separates the text inside the field into the field code and field result. The field code is what defines the general behavior of the field while the field result stores the most recent result when this field is updated by either by Microsoft Word or Aspose.Words. The field result is what is stored in the field and displayed in the document when viewed.

The structure can also be seen below in hierarchical form using the demo project “DocumentExplorer”, which ships with the Aspose.Words installer.

As described in the process above, to convert the field to static text all nodes between the FieldStart and FieldSeparator inclusive, and also the FieldEnd node must be removed.

Please note that this technique cannot be used properly on some fields in the header or footer. For example attempting to convert a PAGE field in a header or footer to static text will cause the same value to appear across all pages. This is because headers and footers are repeated across multiple pages and when they remain as fields they are handled specially so they display the correct result for each page. However upon conversion, the field in the header is transformed into a static run of text. This run of text will be evaluated as if it is the last page in the section which will cause any of PAGE field in the header to display the last page over all pages.

The Code

The implementation which converts fields to static text is described below. The ConvertFieldsToStaticText method can be called at any time within your application. After invoking this method, all of the fields of the specified field type that are contained within the composite node will be transformed into static text.

Example ConvertFieldsToStaticText

This class provides a static method convert fields of a particular type to static text.

[Java]

 

private static class FieldsHelper extends DocumentVisitor

{

    /**

     * Converts any fields of the specified type found in the descendants of the node into static text.

     *

     * @param compositeNode The node in which all descendants of the specified FieldType will be converted to static text.

     * @param targetFieldType The FieldType of the field to convert to static text.

     */

    public static void convertFieldsToStaticText(CompositeNode compositeNode, int targetFieldType) throws Exception

    {

        FieldsHelper helper = new FieldsHelper(targetFieldType);

        compositeNode.accept(helper);

 

    }

 

    private FieldsHelper(int targetFieldType)

    {

        mTargetFieldType = targetFieldType;

    }

 

    public int visitFieldStart(FieldStart fieldStart)

    {

        // We must keep track of the starts and ends of fields incase of any nested fields.

        if (fieldStart.getFieldType() == mTargetFieldType)

        {

            mFieldDepth++;

            fieldStart.remove();

        }

        else

        {

            // This removes the field start if it's inside a field that is being converted.

            CheckDepthAndRemoveNode(fieldStart);

        }

 

        return VisitorAction.CONTINUE;

    }

 

    public int visitFieldSeparator(FieldSeparator fieldSeparator)

    {

        // When visiting a field separator we should decrease the depth level.

        if (fieldSeparator.getFieldType() == mTargetFieldType)

        {

            mFieldDepth--;

            fieldSeparator.remove();

        }

        else

        {

            // This removes the field separator if it's inside a field that is being converted.

            CheckDepthAndRemoveNode(fieldSeparator);

        }

 

        return VisitorAction.CONTINUE;

    }

 

    public int visitFieldEnd(FieldEnd fieldEnd)

    {

        if (fieldEnd.getFieldType() == mTargetFieldType)

            fieldEnd.remove();

        else

            CheckDepthAndRemoveNode(fieldEnd); // This removes the field end if it's inside a field that is being converted.

 

        return VisitorAction.CONTINUE;

    }

 

    public int visitRun(Run run)

    {

        // Remove the run if it is between the FieldStart and FieldSeparator of the field being converted.

        CheckDepthAndRemoveNode(run);

 

        return VisitorAction.CONTINUE;

    }

 

    public int visitParagraphEnd(Paragraph paragraph)

    {

        if (mFieldDepth > 0)

        {

            // The field code that is being converted continues onto another paragraph. We

            // need to copy the remaining content from this paragraph onto the next paragraph.

            Node nextParagraph = paragraph.getNextSibling();

 

            // Skip ahead to the next available paragraph.

            while (nextParagraph != null && nextParagraph.getNodeType() != NodeType.PARAGRAPH)

                nextParagraph = nextParagraph.getNextSibling();

 

            // Copy all of the nodes over. Keep a list of these nodes so we know not to remove them.

            while (paragraph.hasChildNodes())

            {

                mNodesToSkip.add(paragraph.getLastChild());

                ((Paragraph)nextParagraph).prependChild(paragraph.getLastChild());

            }

 

            paragraph.remove();

        }

 

        return VisitorAction.CONTINUE;

    }

 

    public int visitTableStart(Table table)

    {

        CheckDepthAndRemoveNode(table);

 

        return VisitorAction.CONTINUE;

    }

 

    /**

     * Checks whether the node is inside a field or should be skipped and then removes it if necessary.

     */

    private void CheckDepthAndRemoveNode(Node node)

    {

        if (mFieldDepth > 0 && !mNodesToSkip.contains(node))

            node.remove();

    }

 

    private int mFieldDepth = 0;

    private ArrayList mNodesToSkip = new ArrayList();

    private int mTargetFieldType;

}

 

 

The method accepts two parameters, A CompositeNode and a FieldType enumeration. Being able to pass any composite node to this method allows you to convert fields to static text in specific parts of your document only.

For example you can pass a Document object and convert the fields of the specified type from the entire document to static text, or you could pass the Body object of a section and convert only fields found within that body.

When passing a block level node such as a Paragraph, be aware that in some cases fields can span across multiple paragraphs. For instance the FieldCode of a field can contain multiple paragraphs which will cause the FieldEnd to appear in a separate paragraph from the corresponding FieldStart. In this case you will find that a portion of the field code may still remain after the process has finished. If this happens then it is recommended to instead pass the parent of the composite to avoid this.

The FieldType enumeration passed to the method specifies what type of field should be convert to static text. A field of any other type encountered in the document will be left unchanged.

Example FieldsToStaticTextDocument

Shows how to convert all fields of a specified type in a document to static text.

[Java]

 

Document doc = new Document(dataDir + "TestFile.doc");

 

// Pass the appropriate parameters to convert all IF fields encountered in the document (including headers and footers) to static text.

FieldsHelper.convertFieldsToStaticText(doc, FieldType.FIELD_IF);

 

// Save the document with fields transformed to disk.

doc.save(dataDir + "TestFileDocument Out.doc");

 

 

Example FieldsToStaticTextBody

Shows how to convert all fields of a specified type in a body of a document to static text.

[Java]

 

Document doc = new Document(dataDir + "TestFile.doc");

 

// Pass the appropriate parameters to convert PAGE fields encountered to static text only in the body of the first section.

FieldsHelper.convertFieldsToStaticText(doc.getFirstSection().getBody(), FieldType.FIELD_PAGE);

 

// Save the document with fields transformed to disk.

doc.save(dataDir + "TestFileBody Out.doc");

 

 

Example FieldsToStaticTextParagraph

Shows how to convert all fields of a specified type in a paragraph to static text.

[Java]

 

Document doc = new Document(dataDir + "TestFile.doc");

 

// Pass the appropriate parameters to convert all IF fields to static text that are encountered only in the last

// paragraph of the document.

FieldsHelper.convertFieldsToStaticText(doc.getFirstSection().getBody().getLastParagraph(), FieldType.FIELD_IF);

 

// Save the document with fields transformed to disk.

doc.save(dataDir + "TestFileParagraph Out.doc");