Skip to content

Commit

Permalink
Introduced document directives. Added support for referencing assembl…
Browse files Browse the repository at this point in the history
…ies and importing namespaces in views. Examples:

-To reference an assembly, use <%@ Assembly Name="System.Speech" %>.
-To use a namespace, use <%@ Import Namespace="System.Speech.Synthesis" %>.
  • Loading branch information
egonl committed Dec 23, 2017
1 parent 6e18afc commit a2ef61a
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 36 deletions.
14 changes: 10 additions & 4 deletions SharpDocx/CodeBlockBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using SharpDocx.Extensions;
using SharpDocx.Models;

namespace SharpDocx
Expand Down Expand Up @@ -57,7 +58,6 @@ private void AppendCodeBlocks(CharacterMap map, bool replaceCodeWithPlaceholder)
var cb = GetCodeBlock(code);
cb.StartText = map[startTagIndex].Element as Text;
cb.EndText = map[endTagIndex + 1].Element as Text;
cb.Code = code;
CodeBlocks.Add(cb);
mapParts.Add(cb, new MapPart { StartIndex = startTagIndex, EndIndex = endTagIndex + 1});
startTagIndex = map.Text.IndexOf("<%", endTagIndex + 2);
Expand Down Expand Up @@ -107,16 +107,22 @@ private static CodeBlock GetCodeBlock(string code)
{
CodeBlock cb;

if (code.Replace(" ", "").StartsWith("if(") && CodeBlock.GetCurlyBracketLevelIncrement(code) > 0)
code = code.RemoveRedundantWhitespace();

if (code.StartsWith("@"))
{
cb = new Directive(code.Substring(1));
}
else if (code.StartsWith("if(") && CodeBlock.GetCurlyBracketLevelIncrement(code) > 0)
{
cb = new ConditionalCodeBlock
cb = new ConditionalCodeBlock(code)
{
Condition = CodeBlock.GetExpressionInBrackets(code),
};
}
else
{
cb = new CodeBlock();
cb = new CodeBlock(code);
}

return cb;
Expand Down
53 changes: 53 additions & 0 deletions SharpDocx/Directive.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using SharpDocx.Extensions;
using SharpDocx.Models;

namespace SharpDocx
{
public class Directive : CodeBlock
{
public string Name { get; }

public Dictionary<string, string> Attributes { get; } = new Dictionary<string, string>();

public Directive(string code) : base(code)
{
var stringExpressions = new Dictionary<string, string>();
string stringExpression;
var startIndex = 0;

do
{
stringExpression = GetExpressionInApostrophes(code, startIndex);
if (stringExpression != null)
{
// Replace attribute values enclosed in apostrophes with guids to avoid parsing problems later.
// E.g. attribute="The value of 1 + 1 = 2." becomes attribute=041fb3eedc5349d88437082a71f00ee5
var stringId = Guid.NewGuid().ToString("N");
stringExpressions.Add(stringId, stringExpression);
code = code.Replace($"\"{stringExpression}\"", stringId);
startIndex = code.IndexOf(stringId) + stringId.Length;
}
} while (stringExpression != null);

var tagContents = code
.Replace('\n', ' ')
.RemoveRedundantWhitespace()
.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);

Name = tagContents[0].ToLower();

for (var i = 1; i < tagContents.Length; ++i)
{
var attributeContents = tagContents[i].Split('=');

Attributes.Add(
attributeContents[0].ToLower(),
stringExpressions.ContainsKey(attributeContents[1])
? stringExpressions[attributeContents[1]]
: attributeContents[1]);
}
}
}
}
35 changes: 15 additions & 20 deletions SharpDocx/DocumentAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,38 +49,33 @@ internal DocumentAssembly(
}

// Get user defined using directives by calling the static BaseDocument.GetUsingDirectives method.
var usingDirectives = (List<string>) a.Invoke(
baseClass.FullName,
null,
"GetUsingDirectives",
null);
var usingDirectives =
(List<string>) a.Invoke(
baseClass.FullName,
null,
"GetUsingDirectives",
null)
?? new List<string>();


// Get user defined assemblies to reference.
var referencedAssemblies = (List<string>) a.Invoke(
baseClass.FullName,
null,
"GetReferencedAssemblies",
null);
var referencedAssemblies =
(List<string>) a.Invoke(
baseClass.FullName,
null,
"GetReferencedAssemblies",
null)
?? new List<string>();

if (model != null)
{
// Add namespace(s) of Model.
if (usingDirectives == null)
{
usingDirectives = new List<string>();
}

foreach (var type in GetTypes(model.GetType()))
{
usingDirectives.Add($"using {type.Namespace};");
}

// Reference Model assembly/assemblies.
if (referencedAssemblies == null)
{
referencedAssemblies = new List<string>();
}

foreach (var type in GetTypes(model.GetType()))
{
referencedAssemblies.Add(type.Assembly.Location);
Expand Down
52 changes: 47 additions & 5 deletions SharpDocx/DocumentCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,31 +80,44 @@ internal static Assembly Compile(
for (var i = 0; i < codeBlocks.Count; ++i)
{
var cb = codeBlocks[i];
invokeDocumentCodeBody.Append($" CurrentCodeBlock = CodeBlocks[{i}];{Environment.NewLine}");

if (!string.IsNullOrEmpty(cb.Code))
{
if (cb.Code[0] == '=')
{
// Expand <%=SomeVar%> into <% Write(SomeVar); %>
invokeDocumentCodeBody.Append($" CurrentCodeBlock = CodeBlocks[{i}];{Environment.NewLine}");
invokeDocumentCodeBody.Append($" Write({cb.Code.Substring(1)});{Environment.NewLine}");
}
else if (cb is Directive)
{
var directive = (Directive) cb;
if (directive.Name.Equals("import"))
{
AddUsingDirective(directive, usingDirectives);
}
else if (directive.Name.Equals("assembly"))
{
AddReferencedAssembly(directive, referencedAssemblies);
}
}
else if (cb is ConditionalCodeBlock)
{
var ccb = (ConditionalCodeBlock) cb;
var ccb = (ConditionalCodeBlock) cb;
invokeDocumentCodeBody.Append($" CurrentCodeBlock = CodeBlocks[{i}];{Environment.NewLine}");
invokeDocumentCodeBody.Append($" if (!{ccb.Condition}) {{{Environment.NewLine}");
invokeDocumentCodeBody.Append($" DeleteConditionalContent();{Environment.NewLine}");
invokeDocumentCodeBody.Append($" }}{Environment.NewLine}");

invokeDocumentCodeBody.Append($" {cb.Code.TrimStart()}");
invokeDocumentCodeBody.Append($" {cb.Code.TrimStart()}{Environment.NewLine}");
}
else
{
invokeDocumentCodeBody.Append($" {cb.Code.TrimStart()}");
invokeDocumentCodeBody.Append($" CurrentCodeBlock = CodeBlocks[{i}];{Environment.NewLine}");
invokeDocumentCodeBody.Append($" {cb.Code.TrimStart()}{Environment.NewLine}");
}
}

invokeDocumentCodeBody.Append(Environment.NewLine);
}

var modelType = "string";
Expand All @@ -124,6 +137,35 @@ internal static Assembly Compile(
return Compile(script.ToString(), referencedAssemblies);
}

private static void AddUsingDirective(Directive directive, List<string> usingDirectives)
{
// Support for <%@ Import Namespace="System.Collections.Generic" %>
if (!directive.Attributes.ContainsKey("namespace"))
{
throw new Exception($"The Import directive requires a Namespace attribute in '{directive.Code}'.");
}

usingDirectives.Add($"using {directive.Attributes["namespace"]};");
}

private static void AddReferencedAssembly(Directive directive, List<string> referencedAssemblies)
{
// Support for <%@ Assembly Name="System.Speech" %>
if (!directive.Attributes.ContainsKey("name"))
{
throw new Exception($"The Assembly directive requires a Name attribute in '{directive.Code}'.");
}

var assembly = directive.Attributes["name"];
if (!assembly.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) &&
!assembly.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{
assembly = assembly + ".dll";
}

referencedAssemblies.Add(assembly);
}

private static Assembly Compile(
string sourceCode,
List<string> referencedAssemblies)
Expand Down
26 changes: 26 additions & 0 deletions SharpDocx/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Text;

namespace SharpDocx.Extensions
{
public static class StringExtensions
{
public static string RemoveRedundantWhitespace(this string s)
{
var sb = new StringBuilder();
var previousCharIsWhitespace = false;

foreach (var c in s)
{
if (char.IsWhiteSpace(c) && previousCharIsWhitespace)
{
continue;
}

previousCharIsWhitespace = char.IsWhiteSpace(c);
sb.Append(c);
}

return sb.ToString();
}
}
}
47 changes: 43 additions & 4 deletions SharpDocx/Models/CodeBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public class CodeBlock

internal Text StartText, EndText;

public CodeBlock(string code)
{
Code = code;
}

internal void RemoveEmptyParagraphs()
{
var startParagraph = StartText.GetParent<Paragraph>();
Expand Down Expand Up @@ -49,10 +54,10 @@ public static int GetCurlyBracketLevelIncrement(string code)
return increment;
}

public static string GetExpressionInBrackets(string code, int startIndex = 0)
public static string GetExpression(string code, char startExpression, char endExpression, int startIndex = 0)
{
// Note: same issue as above.
startIndex = code.IndexOf("(", startIndex);
startIndex = code.IndexOf(startExpression, startIndex);
if (startIndex == -1)
{
return null;
Expand All @@ -64,11 +69,11 @@ public static string GetExpressionInBrackets(string code, int startIndex = 0)
{
expression.Append(code[i]);

if (code[i] == '(')
if (code[i] == startExpression)
{
++increment;
}
else if (code[i] == ')')
else if (code[i] == endExpression)
{
--increment;
}
Expand All @@ -81,5 +86,39 @@ public static string GetExpressionInBrackets(string code, int startIndex = 0)

return null;
}

public static string GetExpressionInBrackets(string code, int startIndex = 0)
{
return GetExpression(code, '(', ')', startIndex);
}

public static string GetExpression(string code, string startOrEndTag, int startIndex = 0, bool removeStartAndEndTag = true)
{
// The start/end tag can be escaped with '\'.
code = code.Replace("\\" + startOrEndTag, "b260231509a148aa9d751a5a9d79abd7");

startIndex = code.IndexOf(startOrEndTag, startIndex);
if (startIndex == -1)
{
return null;
}

var endIndex = code.IndexOf(startOrEndTag, startIndex + startOrEndTag.Length);
if (endIndex == -1)
{
return null;
}

code = removeStartAndEndTag
? code.Substring(startIndex + 1, endIndex - startIndex - 1)
: code.Substring(startIndex, endIndex - startIndex + 1);

return code.Replace("b260231509a148aa9d751a5a9d79abd7", "\\" + startOrEndTag);
}

public static string GetExpressionInApostrophes(string code, int startIndex = 0, bool removeApostrophes = true)
{
return GetExpression(code, "\"", startIndex, removeApostrophes);
}
}
}
7 changes: 6 additions & 1 deletion SharpDocx/Models/ConditionalCodeBlock.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DocumentFormat.OpenXml.Wordprocessing;
using System.Runtime.CompilerServices;
using DocumentFormat.OpenXml.Wordprocessing;

namespace SharpDocx.Models
{
Expand All @@ -10,5 +11,9 @@ public class ConditionalCodeBlock : CodeBlock
public string Condition { get; set; }

public Text EndConditionalPart { get; set; }

public ConditionalCodeBlock(string code) : base(code)
{
}
}
}
2 changes: 2 additions & 0 deletions SharpDocx/SharpDocx v3.5.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
<ItemGroup>
<Compile Include="CharacterMap.cs" />
<Compile Include="CodeBlockBuilder.cs" />
<Compile Include="Directive.cs" />
<Compile Include="ElementAppender.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Ide.cs" />
<Compile Include="ImageHelper.cs" />
Expand Down
2 changes: 2 additions & 0 deletions SharpDocx/SharpDocx v4.5.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
<ItemGroup>
<Compile Include="CharacterMap.cs" />
<Compile Include="CodeBlockBuilder.cs" />
<Compile Include="Directive.cs" />
<Compile Include="ElementAppender.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Ide.cs" />
<Compile Include="ImageHelper.cs" />
Expand Down
6 changes: 4 additions & 2 deletions SharpDocx/TODO.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
-Insert merge fields.
-Multiple calls to write in the same code block should be handled correctly.
-Insert merge fields.
-Directive sample.

Consider:
-Show inheritance model in Tutorial (see also https://msdn.microsoft.com/en-us/library/ms178138.aspx).
Expand All @@ -11,4 +13,4 @@ Consider:
-Create samples with a nice form / barcode / complex model (maybe a model based on https://www.theimdbapi.org/).

Won't fix:
-Support contructed generic types as Model in .NET v3.5.
-Support contructed generic types as Model in .NET v3.5.

0 comments on commit a2ef61a

Please sign in to comment.