Generating OpenXml documents

Dec 12, 2010 at 8:32 PM

Hi there,

I love Razor in MVC and thought I would try to generate an OpenXml document with it (we need a document templating engine). It works beautifully if I don't have a loop :) I am having a problem with loops though.

 If I type into this word

 

Name : @Model.Name made by Razor.

@foreach(var disb in Model.Children){

@disb.Name

}

and run it, I get an exception "Encountered end tag 'w:t' with no matching start tag.  Are your start/end tags properly balanced?". I am guessing that this is because the xml from word is not the way I would do MVC HTML, but the tags would balance if the loop ran.
The openxml (elided for clarity) looks like this :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 wp14">
	<w:body>
		<w:p w:rsidR="00194E6E" w:rsidRPr="005939D1" w:rsidRDefault="00E64FEC" w:rsidP="005939D1">
			<w:r w:rsidRPr="005939D1">
				<w:t>@foreach(var disb in Model.Children){</w:t>
			</w:r>
			<w:r w:rsidRPr="005939D1">
				<w:t>@disb.Name</w:t>
			</w:r>
			<w:r w:rsidRPr="005939D1">
				<w:t>}</w:t>
			</w:r>
		</w:p>
	</w:body>
</w:document>

I know this is an off the wall request, but can I specify a different parser perhaps (the default HTML one might well be inappropriate, of course)? Should I make my own ? The tags should all balance out after the loop has run.

Thanks 

The full exception is :
RazorEngine.Templating.TemplateParsingException was unhandled
  Message=Encountered end tag 'w:t' with no matching start tag.  Are your start/end tags properly balanced?
  Source=RazorEngine.Core
  Column=1513
  Line=1
  StackTrace:
       at RazorEngine.Compilation.CSharpRazorCodeGenerator.VisitError(RazorError err) in C:\TFS\play\Razor\RazorEngine.Core\Compilation\CSharpRazorCodeGenerator.cs:line 32
       at System.Web.Razor.Parser.ParserContext.OnError(SourceLocation location, String message)
       at System.Web.Razor.Parser.ParserContext.OnError(SourceLocation location, String message, Object[] args)
       at System.Web.Razor.Parser.ParserBase.OnError(SourceLocation location, String message, Object[] args)
       at System.Web.Razor.Parser.HtmlMarkupParser.ParseEndTag(Stack`1 tags, TagInfo tag, Boolean acceptUnmatchedEndTag)
       at System.Web.Razor.Parser.HtmlMarkupParser.ParseTagBlock(Boolean inDocument)
       at System.Web.Razor.Parser.HtmlMarkupParser.ParseBlock()
       at System.Web.Razor.Parser.ParserBase.ParseBlockWithOtherParser(SpanFactory previousSpanFactory, Boolean collectTransitionToken)
       at System.Web.Razor.Parser.CSharpCodeParser.ParseStatement(CodeBlockInfo block)
       at System.Web.Razor.Parser.CSharpCodeParser.ParseCodeBlock(CodeBlockInfo block, Boolean bracesAreMetacode)
       at System.Web.Razor.Parser.CSharpCodeParser.ParseControlFlowBody(CodeBlockInfo block)
       at System.Web.Razor.Parser.CSharpCodeParser.ParseConditionalBlockStatement(CodeBlockInfo block)
       at System.Web.Razor.Parser.CSharpCodeParser.<>c__DisplayClass3.b__2(CodeBlockInfo block)
       at System.Web.Razor.Parser.CSharpCodeParser.ParseBlock()
       at System.Web.Razor.Parser.ParserBase.ParseBlockWithOtherParser(SpanFactory previousSpanFactory, Boolean collectTransitionToken)
       at System.Web.Razor.Parser.HtmlMarkupParser.TryStartCodeParser(Boolean isSingleLineMarkup, Boolean documentLevel)
       at System.Web.Razor.Parser.HtmlMarkupParser.ParseRootBlock(Tuple`2 nestingSequences, Boolean caseSensitive)
       at System.Web.Razor.Parser.HtmlMarkupParser.ParseDocument()
       at System.Web.Razor.Parser.RazorParser.Parse(LookaheadTextReader input, ParserVisitor visitor)
       at System.Web.Razor.Parser.RazorParser.Parse(TextReader input, ParserVisitor visitor)
       at RazorEngine.Compilation.RazorCompiler.ParseTemplate(String template, RazorParser parser, ParserVisitor visitor) in C:\TFS\play\Razor\RazorEngine.Core\Compilation\RazorCompiler.cs:line 276
       at RazorEngine.Compilation.RazorCompiler.CompileTemplate(String className, String template, Type modelType) in C:\TFS\play\Razor\RazorEngine.Core\Compilation\RazorCompiler.cs:line 150
       at RazorEngine.Compilation.RazorCompiler.CreateTemplate(String template, Type modelType) in C:\TFS\play\Razor\RazorEngine.Core\Compilation\RazorCompiler.cs:line 176
       at RazorEngine.Templating.TemplateService.GetTemplate(String template, Type modelType, String name) in C:\TFS\play\Razor\RazorEngine.Core\Templating\TemplateService.cs:line 51
       at RazorEngine.Templating.TemplateService.Parse[T](String template, T model, String name) in C:\TFS\play\Razor\RazorEngine.Core\Templating\TemplateService.cs:line 85
       at RazorEngine.Razor.Parse[T](String template, T model, String name) in C:\TFS\play\Razor\RazorEngine.Core\Razor.cs:line 61
       at RazorApp.Program.Main(String[] args) in c:\TFS\play\RazorApp\RazorApp\Program.cs:line 23
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

Coordinator
Dec 13, 2010 at 7:29 PM

Hi,

The Microsoft implementation of the CSharpRazorCodeGenerator actually swallows any errors that occur through parsing.  What might work for you is if you create a new ILanguageProvider, and have it return that default implementation instead of our subclassed version, e.g.:

using System.CodeDom.Compiler;
using System.Web.Razor;

using Microsoft.CSharp;

public class MyCSharpLanguageProvider : ILanguageProvider
{
  #region Methods
  public RazorCodeLanguage CreateLanguageService()
  {
    return new System.Web.Razor.CSharpRazorCodeLanguage();
  }
  
  public CodeDomProvider CreateCodeDomProvider()
  {
    return new CSharpCodeProvider();
  }
  #endregion
}

You can then assign the language provider through either the Razor static class, or the TemplateService (if you are using one directly):

Razor.SetLanguageProvider(new MyCSharpLanguageProvider());
Until we potentially add support for Strict vs. Fault Friendly parsing mode, this may have to do.

Dec 13, 2010 at 10:32 PM

Thanks Antaris, that got me further. My next issue is that because the } is in the middle of an openXml tag, it appears that the Razor parser has decided that the brace is not code, so this part of the openXml doument

<w:r w:rsidRPr="005939D1">
	<w:t>}</w:t>
</w:r>

produces 

                WriteLiteral("<w:p w:rsidR=\"002504E8\" w:rsidRDefault=\"00FE3C1C\" w:rsidP=\"005939D1\"><w:r><w:t>}</w:t></w:r></w:p>");

in the generated code. Is there any inverse of escaping, i.e. is there a way I can specify that something IS code ? I see I can escape things to not be code (@@) etc, but it appears I need the reverse. I tried @} and a few variations, but with the same result.

Many Thanks,

 

Coordinator
Dec 15, 2010 at 4:26 PM

This is going to be a little trickier.  The reality is you're actually approaching this from the opposite direction, whereby you are using Word to essentially generate your markup around your tags, instead of using Razor to generate your markup after it has processed its code.

 

Would it be better to use Razor to generate the raw document.xml file, without using Word? I would expect it to work that way, as doing it the current way it is generating xml nodes for the placement of the Razor code, when those nodes are going to be redundant after you've parsed the file anyway?

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 wp14">
	<w:body>
		<w:p w:rsidR="00194E6E" w:rsidRPr="005939D1" w:rsidRDefault="00E64FEC" w:rsidP="005939D1">
			@foreach(var disb in Model.Children){
			<w:r w:rsidRPr="005939D1">
				<w:t>@disb.Name</w:t>
			</w:r>
			}
		</w:p>
	</w:body>
</w:document>

Notice how I don't have the additional <w:r> elements for the Razor code.  I don't think you'd be able to use Word as the generator for building your templates as it takes the Razor code as literal text (hence it generating the additional elements).

Dec 15, 2010 at 5:29 PM

Hi Antaris,

Thanks for the response. You are correct, if one authors the openxml directly it works perfectly, and of course that's how Razor was designed to be used. I was hoping that users could edit their templates in word and only see the C#, and no xml.

Next of course, I will be asking for support for Expression Web and FrontPage, lol.

I will regrettably have to explore other templating engines for document generation, and keep my Razor love in MVC.

Many thanks for all your Help.