Is there a way to dinamically specify the uri in ImportTemplate or IncludeTemplate

Jun 16, 2010 at 9:37 PM

Is there a way to dinamically specify the uri in ImportTemplate or IncludeTemplate?  I would like to create a ForEach loop to include files in which the name of the file is an element of the current node.

Coordinator
Jun 17, 2010 at 7:03 AM

Sorry, that is not currently possible. For technical reasons IncludeTemplate & ImportTemplate are processed before the rest of the page content is processed. Therefore it can never refer to a for-each's context-node.

If the number of sub-templates is small, an alternative could be to include all of them inside the for-each and include/exclude them using the If-tag (conditional rendering).

Robert

Jun 17, 2010 at 4:26 PM

I was able to do what I wanted by adding the following code 

I added a new tag called IncludeFile that has two attributes, path and uri.  This basically allows me to include a "static" word file into the template with a file name that is dinamically specified by the path expression or by a fixed file name in the uri.

It is working for me, but can you take a look to see if this is the correct way to implement this, and if you like, include it in a future release?

// fleXdoc.xsd

    <xs:complexType name="IncludeFile">
        <xs:attribute name="path" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>xpath-query to the node containing the file name. When specified AND a matching node was found, then 'uri' is ignored.</xs:documentation>
            </xs:annotation>
        </xs:attribute>
        <xs:attribute name="uri" type="xs:string" use="optional">
            <xs:annotation>
                <xs:documentation>Uri to an file. Only used when 'path'was not specified or did not match with a node.</xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>
    <xs:element name="IncludeFile" type="IncludeFile"/>

 

 // XmlQueryProcessor.xslt
 

    <xsl:template match="w:customXml[@w:uri = 'urn:fleXdoc' and @w:element='IncludeFile']">
        <xsl:param name="datacontext" />
        <xsl:param name="listindex" /> 
         <xsl:variable name="path" select="w:customXmlPr/w:attr[@w:name='path']/@w:val" /> 
         <xsl:choose>
            <xsl:when test="$path">
                <xsl:variable name="filedata" select="ext:Evaluate($datacontext, $path, $dataNsPrefix, $dataNs, &quot;IncludeFile&quot;)" />
                <xsl:choose>
                    <xsl:when test="$filedata">
                        <xsl:copy>
                            <xsl:apply-templates select="@*" />
                            <xsl:element name="customXmlPr" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
                                <xsl:copy-of select="w:customXmlPr/w:placeholder"/>
                                <xsl:copy-of select="w:customXmlPr/w:attr[@w:name = 'path']"/>
                                <xsl:element name="attr" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
                                    <xsl:attribute name="name" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
                                        <xsl:text>data</xsl:text>
                                    </xsl:attribute>
                                    <xsl:attribute name="val" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
                                        <xsl:value-of select="$filedata"/>
                                    </xsl:attribute>
                                </xsl:element>
                            </xsl:element>
                        </xsl:copy>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="." />
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy-of select="." />
            </xsl:otherwise>
        </xsl:choose> 
     </xsl:template>
 

   // fleXdocTemplateProcessor.cs 
 
       private const string ELEMENT_INCLUDEFILE = "IncludeFile";

 
        protected override void PostProcessCustomXmlElement(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, string namespaceUri, string elementName, CustomXmlProperties properties, BuildOptions options)
        {
            if (namespaceUri == NS_FLEXDOC)
            {
                switch (elementName)
                {
                    case ELEMENT_IMGOF:
                        PostProcessTag_ImgOf(doc, part, element, properties, options);
                        break;
                    case ELEMENT_INCLUDEFILE:
                        PostProcessTag_IncludeFile(doc, part, element, properties, options);
                        break;
                }
            }
        } 
 

 

        private void PostProcessTag_IncludeFile(WordprocessingDocument doc, OpenXmlPart part, OpenXmlCompositeElement element, CustomXmlProperties properties, BuildOptions options)
        {
            // If the IncludeFile-tag is not a direct child of the part-body
            bool parentIsParagraph = (element.Parent is Paragraph);
            if (parentIsParagraph)
            {
                Debug.WriteLine("FlexDocTemplateProcessor:PostProcessTag_IncludeFile: Warning: IncludeFile cannot be contained within a paragraph and has therefore been moved right after the paragraph");
                OpenXmlCompositeElement newElement = (OpenXmlCompositeElement)element.CloneNode(true);
                element.Parent.InsertAfterSelf<OpenXmlCompositeElement>(newElement);
                element.Remove();
                element = newElement;
                part.RootElement.Save();
            }
            if (properties != null)
            {
 


                // See if data already contains something: this means that path was specified an matched with a node
                string templatePath = String.Empty;
                if (!String.IsNullOrEmpty((from p in properties.Elements<CustomXmlAttribute>()
                                       where p.Name == "path"
                                       select p.Val.Value).FirstOrDefault()))
                {
                    //we replace the path with the data
                    templatePath = (from p in properties.Elements<CustomXmlAttribute>()
                                           where p.Name == "data"
                                           select p.Val.Value).FirstOrDefault();
 


                }
                else
                {
                   // No path specified or it matched no node, now try 'uri'
                    string uri = (from p in properties.Elements<CustomXmlAttribute>()
                                           where p.Name == "uri"
                                           select p.Val.Value).FirstOrDefault();
                    if (!String.IsNullOrEmpty(uri))
                    {
                        // Resolve the uri
                        uri = OnResourceUriResolve(uri);
                    }
                }
 


                if (!String.IsNullOrEmpty(templatePath))
                {
                    // Resolve the uri
                    templatePath = OnResourceUriResolve(templatePath);
 


                    string absoluteDataPath = (from p in properties.Elements<CustomXmlAttribute>()
                                               where p.Name == "absolutePath"
                                               select p.Val.Value).FirstOrDefault();
 


                    if (String.IsNullOrEmpty(absoluteDataPath))
                    {
                        absoluteDataPath = "."; // current
                    }
 


                    // Create the buildoptions for the template
                    BuildOptions templateOptions = new BuildOptions();
                    templateOptions.NamespacePrefix = options.NamespacePrefix;
                    templateOptions.NamespaceUri = options.NamespaceUri;
                    templateOptions.RenderCulture = options.RenderCulture;
                    templateOptions.ValidatePackage = options.ValidatePackage;
 


                    // Determine xpath-navigator for template
                    XmlNamespaceManager nsMgr = new XmlNamespaceManager(options.Data.NameTable);
                    nsMgr.AddNamespace(options.NamespacePrefix, options.NamespaceUri);
                    templateOptions.Data = options.Data.SelectSingleNode(absoluteDataPath, nsMgr);
                    if (templateOptions.Data == null)
                    {
                        Trace.TraceWarning("IncludeFile ({0}): absoluteDataPath ({1})resulted in an empty node-set", templatePath, absoluteDataPath);
                    }
 


                    // Process the template
                    MemoryStream templateStream = IOHelper.GetUri(templatePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                    WordprocessingDocument docTemplate = WordprocessingDocument.Open(templateStream, true);
 


                    using (fleXdocTemplateProcessor fdProcessor = new fleXdocTemplateProcessor())
                    {
                        fdProcessor.ResourceUriResolve += delegate(object sender, ResolveResourceUriEventArgs args)
                        {
                            return OnResourceUriResolve(args.ResourceUri);
                        };
                        fdProcessor.ProcessWordprocessingDocument(docTemplate, templateOptions);
                    }
 


                    // Import external parts for the maindocumentpart of the template
                    ImportDrawingParts(docTemplate.MainDocumentPart, doc, part);
                    ImportHyperlinkParts(docTemplate.MainDocumentPart, doc, part);
 


                    // Remove any child sectionproperties
                    List<SectionProperties> sectPrList = docTemplate.MainDocumentPart.Document.Body.Descendants<SectionProperties>().ToList();
                    for (int index = 0; index < sectPrList.Count; index++)
                    {
                        sectPrList[index].Remove();
                    }
                    docTemplate.MainDocumentPart.RootElement.Save();
 


                    // Import all children of the body of the maindocumentpart of the template to be imported
                    foreach (OpenXmlElement templateChild in docTemplate.MainDocumentPart.Document.Body.ChildElements.Reverse())
                    {
                        element.InsertAfterSelf(templateChild.CloneNode(true));
                    }
                }
            }
            element.Remove();
 

 

            part.RootElement.Save();
        }
 

 

 

 

 

 

 

 

 

 

 

 

 

 

Coordinator
Jun 17, 2010 at 6:41 PM

Excellent job! Looks ok to me. However, for now keep your own copy of the code, because I'm not currently actively working on a new release (adoption of your code may take a while).

ALso, when inspecting your code I also remember why I chose to preprocess the ImportTemplate and IncludeTemplate: when in a for-each, the processing of the subtemplate is done only once so that while looping through the node-set, only the resulting output needs to be cloned.

Furthermore the data-XML is processed using xslt, which also means the context-node of the foreach is only available during the xslt-process. From within the xslt it's not possible to completely process another template (perhaps with some really dirty code, but I wanted to keep the code maintainable ;)). Because it's not possible to get a context-node for ImportTemplate and IncludeTemplate, they both specify the context-node using absolutePath instead of path.

Again, good work! :)

Coordinator
Jul 23, 2013 at 6:41 PM
Come check out the successor of fleXdoc: Docati!
It supports Word 2010 and 2013 as well and runs in the cloud.

I sure hope to welcome you as a Docati user as well! :-)

http://www.docati.com