Is it possible to format a dynamic text?

Aug 28, 2012 at 12:02 AM
Edited Aug 28, 2012 at 12:03 AM

Is it possible to format a dynamic text?

Example: If I need to format 'Order' with bold:

 <field> <listFields> <field orderName="Test <b>Order</b>" ... />

 ....

The output would be:

Test Order

Regards,

Jorge

 


Aug 28, 2012 at 5:33 PM
Edited Aug 29, 2012 at 2:06 AM

I suggest replace the OpnXmlHelper.css method CreateRun by this one bellow where I use HtmlConverter (http://html2openxml.codeplex.com/documentation) to create OpnXml paragraphs:

 

private static Run CreateRun(MainDocumentPart mainPart, OpenXmlCompositeElement openXmlCompositeElement, string content)
        {   
            RunProperties runProperties = openXmlCompositeElement.Descendants<RunProperties>().FirstOrDefault();
            Run run = null;

	     HtmlConverter html = new HtmlConverter(mainPart);            
             IList<OpenXmlCompositeElement> paragraphs = html.Parse("<html>"+content+"</html>");            
             if (runProperties != null)            
		{                
			OpenXmlElement[] eles = new OpenXmlElement[1 + paragraphs.Count];                
			eles[0] = runProperties.CloneNode(true);                
			for (int i = 0; i < paragraphs.Count; i++)                
			{                    
				eles[i + 1] = paragraphs[i];                
			}                
			run = new Run(eles);            
		}            
		else            
		{                
			run = new Run(paragraphs);            
		}
            return run;
        }

 

However, to implement this solution I had to change the method signature to include "MainDocumentPart mainPart". To do it I had to change a lot of methods up to DocumentGenerator definition. This was not the best approach but proved it works.

Atul,

what if you provide an abstraction to the CreateRun, such as an interface that allows us to drop default strategy by things like this?

I mean, if you provide a delegate to run creation we could just write a new one like the above without having to hack you code. Even better, this delegate could receive the document itself to perfom other things if wanted. 

  By now, if you say to me how to recover the Document main part from 

openXmlCompositeElement

I would appreciate. It minimizes the impac of changes in API signatures.

Aug 29, 2012 at 3:02 AM
Edited Aug 29, 2012 at 3:55 AM

A better solution would be, I think:

1) Define an interface:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;

namespace WordDocumentGenerator.Library
{
    public interface IRunFactory
    {
        /// <summary>
        /// Creates the run.
        /// </summary>
        /// <param name="openXmlCompositeElement">The open XML composite element.</param>
        /// <param name="runProperties">The run properties.</param>
        /// <param name="content">The content.</param>
        /// <returns></returns>
        Run createRun(OpenXmlCompositeElement openXmlCompositeElement, RunProperties runProperties, string content);
    }
}

 

2) Provide a default implementation:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;

namespace WordDocumentGenerator.Library
{
    public class RunFactoryDefault : IRunFactory
    {
        public Run createRun(OpenXmlCompositeElement openXmlCompositeElement, RunProperties runProperties, string content)
        {
            Text text = new Text(content);
            return runProperties != null ? new Run(runProperties.CloneNode(true), text) : new Run(text);
        }
    }
}

3) Perform some changes to OpenXmlHelper.cs (in this case I preffer WordDocGenerator core change a little bit to acommodate this requirement)

...

        #region Memebers

        private readonly string NamespaceUri = string.Empty;
        // Add the default runner factory.
        public static IRunFactory RunFactory = new RunFactoryDefault();

        #endregion

...

        private static Run CreateRun(OpenXmlCompositeElement openXmlCompositeElement, string content)
        {
            return RunFactory.createRun(openXmlCompositeElement, openXmlCompositeElement.Descendants<RunProperties>().FirstOrDefault(), content);
        }
4) To use HTML in tag contents, include Html2OpenXml DLL and add the following implementation to your project (this approach avoid unecessary CreateRun signature changes):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;
using NotesFor.HtmlToOpenXml;

namespace WordDocumentGenerator.Library
{
    public class RunFactoryHtml : IRunFactory
    {
        public Run createRun(OpenXmlCompositeElement openXmlCompositeElement, RunProperties runProperties, string content)
        {
            IList<OpenXmlElement> paragraphs;
            OpenXmlElement ele = openXmlCompositeElement.Parent;
            while (ele.Parent != null)
            {
                ele = ele.Parent;
            }
            if (ele is Document)
            {
                HtmlConverter html = new HtmlConverter(((Document)ele).MainDocumentPart);
                paragraphs = new List<OpenXmlElement>(html.Parse(content));
            }
            else
            {
                paragraphs = new List<OpenXmlElement>(new Text(content));
            }

            if (runProperties != null)
            {
                paragraphs.Insert(0,runProperties.CloneNode(true));
            }
            return new Run(paragraphs);
        }
    }
}

5) Before XXX.GenerateDocument() add:

OpenXmlHelper.RunFactory = new RunFactoryHtml();

6) That is it! Your template engine works with HTML (restricted to Html2OpenXml tags, but dynamically formated in HTML).

Atul,

Both projects (WordDocGenerator/Html2OpenXml) are priceless, and could work together.

This was my contribution to make them work without unecessary project mutual references. I suppose the use of IRunFactory in WordDocGenerator would allow integration of uncountable extensions to come.

Please consider the add to WordDoGenerator core, and make it more extensible. 

Regards,

Thiago.

Sep 4, 2012 at 6:33 PM

Thiago, thanks for the suggestion, it's a good idea, but sometimes the generated document can not be open. The Word shows a message like this:

"The file is corrupt and cannot be opened".

Atul, could you help us?

The idea is simple: insert a html converter into your code (WordDocumentGenerator).

The projetct http://html2openxml.codeplex.com/documentation implements a HtmlConverter. So, how can we join the code below into your code?

From: http://html2openxml.codeplex.com/documentation

 

static void Main(string[] args)
{
     const string filename = "test.docx";
     string html = Properties.Resources.DemoHtml;

     if (File.Exists(filename)) File.Delete(filename);

     using (MemoryStream generatedDocument = new MemoryStream())
     {
          using (WordprocessingDocument package = WordprocessingDocument.Create(generatedDocument, WordprocessingDocumentType.Document))
          {
               MainDocumentPart mainPart = package.MainDocumentPart;
               if (mainPart == null)
               {
                    mainPart = package.AddMainDocumentPart();
                    new Document(new Body()).Save(mainPart);
               }

               HtmlConverter converter = new HtmlConverter(mainPart);
               Body body = mainPart.Document.Body;

               var paragraphs = converter.Parse(html);
               for (int i = 0; i < paragraphs.Count; i++)
               {
                    body.Append(paragraphs[i]);
               }

               mainPart.Document.Save();
          }

          File.WriteAllBytes(filename, generatedDocument.ToArray());
     }

     System.Diagnostics.Process.Start(filename);
}

Thank you very much again!

Jorge

Sep 4, 2012 at 9:07 PM
Edited Sep 4, 2012 at 9:08 PM

An updated version for CreateRun body:

 

            RunProperties clone = runProperties != null ? runProperties.CloneNode(true) as RunProperties : null;

            OpenXmlElement ele = openXmlCompositeElement.Parent;
            while (ele.Parent != null)
            {
                ele = ele.Parent;
            }
            IList<Run> paragraphs = new List<Run>();
            if (ele is Document)
            {
                HtmlConverter html = new HtmlConverter(((Document)ele).MainDocumentPart);
                foreach (OpenXmlCompositeElement e in html.Parse(content))
                {
                    if (e is Table)
                    {
                        return new Run(new Text("Element is not allowed."));
                    }
                    else
                    {
                        foreach (OpenXmlElement child in e.ChildElements)
                        {
                            paragraphs.Add(clone != null ? new Run(clone.CloneNode(true), child.CloneNode(true)) : new Run(child.CloneNode(true)));
                        }
                    }
                }
            }
            if (paragraphs.Count > 0)
            {
                return new Run(paragraphs);
            }
            return clone != null ? new Run(clone,new Text(content)) : new Run(new Text(content));

 

Works better then the previous one.

 

Tables, however cannot be child on SdtContentRun, this is the reason for Table restriction.

BTW, the CreateRun signature could also be generalized to return OpenXmlElement instead of Run, this would allow use the template engine with other things than plain texts.

Just a suggestion.

Jun 10, 2014 at 6:01 AM
Edited Jun 11, 2014 at 1:41 AM
Hi thiagosaint,

are you able to share your version here or through GIT?

I followed your steps, but I get an error when I open the document: "The file xxx cannot be opened because there are problems with contents." The details show "Details: Unspecified error Location: Part: /word/document.xml, Line: 1, Column: 0". I only added a "<p>Test</p>" to a field in my word document.

If I have nothing on the field, the document opens without problems.
Jun 18, 2014 at 12:11 AM
I have figured out the cause of my problem. My control control was plain text, rather than rich text.