Skip to content

Commit

Permalink
Merge pull request #69 from egonl/2.5-beta
Browse files Browse the repository at this point in the history
feat: added view stream support
  • Loading branch information
egonl authored Apr 24, 2024
2 parents 107db8f + 3b216c2 commit 96d0b9c
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 61 deletions.
Binary file modified Samples/Documents/Inheritance.docx
Binary file not shown.
Binary file modified Samples/Documents/Tutorial.docx
Binary file not shown.
1 change: 1 addition & 0 deletions Samples/SampleProjects/Inheritance/Inheritance.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<!-- Possible TargetFrameworks: net35, net40, net45, net46, net47, net48, netstandard2.0, netcoreapp3.1, net5.0, net6.0 -->
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<RootNamespace>Inheritance</RootNamespace>
<LangVersion>8</LangVersion>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
Expand Down
37 changes: 22 additions & 15 deletions Samples/SampleProjects/Inheritance/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.IO;
using SharpDocx;
#if NET35
using SharpDocx.Extensions;
#endif

namespace Inheritance
{
Expand All @@ -19,22 +22,26 @@ private static void Main()

Ide.Start(viewPath, documentPath, null, typeof(MyDocument), f => ((MyDocument) f).MyProperty = "The code", documentViewer);
#else
var myDocument = DocumentFactory.Create<MyDocument>(viewPath);
// 1 - It's possible to use a file or a stream for a view.

// 1a - Use a file for a view
//var myDocument = DocumentFactory.Create<MyDocument>(viewPath);

// 1b - Or use a stream for a view
using var viewStream = File.OpenRead(viewPath);
var myDocument = DocumentFactory.Create<MyDocument>(viewStream);

myDocument.MyProperty = "The Code";

// It's possible to generate a file or a stream.

// 1. Generate a file
// myDocument.Generate(documentPath);

//2. Generate an output stream.
using (var outputStream = myDocument.Generate())
{
using (var outputFile = File.Open(documentPath, FileMode.Create))
{
outputFile.Write(outputStream.GetBuffer(), 0, (int)outputStream.Length);
}
}

// 2 - It's also possible to generate a file or a stream.

// 2a - Generate a file
//myDocument.Generate(documentPath);

// 2b - Or generate an output stream.
using var outputStream = myDocument.Generate();
using var outputFile = File.Open(documentPath, FileMode.Create);
outputStream.CopyTo(outputFile);
#endif
}
}
Expand Down
8 changes: 8 additions & 0 deletions Samples/SampleProjects/Tutorial/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.IO;
using SharpDocx;
#if NET35 || NET40
using System.Net;
#endif

namespace Tutorial
{
Expand All @@ -11,6 +14,11 @@ internal class Program

private static void Main()
{
#if NET35 || NET40
// The Tutorial fetches an image from https://github.com/egonl/SharpDocx/blob/master/Samples/Images/test3.emf?raw=true,
// which requires TLS 1.2.
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
#endif
var viewPath = $"{BasePath}/Views/Tutorial.cs.docx";
var documentPath = $"{BasePath}/Documents/Tutorial.docx";
var imageDirectory = $"{BasePath}/Images";
Expand Down
Binary file modified Samples/Views/Inheritance.cs.docx
Binary file not shown.
Binary file modified Samples/Views/Tutorial.cs.docx
Binary file not shown.
27 changes: 22 additions & 5 deletions SharpDocx/DocumentAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,35 @@ namespace SharpDocx
{
internal class DocumentAssembly
{
private readonly Assembly _assembly;
private readonly string _className;
private Assembly _assembly;
private string _className;

internal DocumentAssembly(
Stream viewStream,
Type baseClass,
Type modelType)
{
Create(viewStream, baseClass, modelType);
}

internal DocumentAssembly(
string viewPath,
Type baseClass,
Type modelType)
{
using var viewStream = File.OpenRead(viewPath);
Create(viewStream, baseClass, modelType);
}


private void Create(
Stream viewStream,
Type baseClass,
Type modelType)
{
if (baseClass == null)
{
throw new ArgumentNullException(nameof(baseClass));
throw new ArgumentNullException(nameof(baseClass));
}

// Load base class assembly.
Expand Down Expand Up @@ -51,7 +69,6 @@ internal DocumentAssembly(
"GetUsingDirectives",
null)
?? new List<string>();


// Get user defined assemblies to reference.
var referencedAssemblies =
Expand Down Expand Up @@ -82,7 +99,7 @@ internal DocumentAssembly(

// Create an assembly for this class.
_assembly = DocumentCompiler.Compile(
viewPath,
viewStream,
_className,
baseClass.Name,
modelType,
Expand Down
58 changes: 46 additions & 12 deletions SharpDocx/DocumentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public abstract class DocumentBase
{
public string ImageDirectory { get; set; }

public string ViewPath { get; private set; }
public string ViewPath { get; internal set; }

public Stream ViewStream { get; internal set; }

protected List<CodeBlock> CodeBlocks;

Expand All @@ -45,7 +47,7 @@ public abstract class DocumentBase
public void Generate(string documentPath, object model = null)
{
documentPath = Path.GetFullPath(documentPath);
File.Copy(ViewPath, documentPath, true);
using var documentStream = GetDocumentStream(documentPath);

#if NET35 && SUPPORT_MULTI_THREADING_AND_LARGE_DOCUMENTS_IN_NET35
// Due to a bug in System.IO.Packaging writing large uncompressed parts (>10MB) isn't thread safe in .NET 3.5.
Expand All @@ -56,7 +58,7 @@ public void Generate(string documentPath, object model = null)
try
{
#endif
using (Package = WordprocessingDocument.Open(documentPath, true))
using (Package = WordprocessingDocument.Open(documentStream, true))
{
GenerateInternal(model);
}
Expand All @@ -70,11 +72,9 @@ public void Generate(string documentPath, object model = null)
#endif
}

public MemoryStream Generate(object model = null)
public Stream Generate(object model = null)
{
var viewBytes = File.ReadAllBytes(ViewPath);
MemoryStream outputstream = new MemoryStream();
outputstream.Write(viewBytes, 0, viewBytes.Length);
var documentStream = GetDocumentStream();

#if NET35 && SUPPORT_MULTI_THREADING_AND_LARGE_DOCUMENTS_IN_NET35
// Due to a bug in System.IO.Packaging writing large uncompressed parts (>10MB) isn't thread safe in .NET 3.5.
Expand All @@ -85,7 +85,7 @@ public MemoryStream Generate(object model = null)
try
{
#endif
using (Package = WordprocessingDocument.Open(outputstream, true))
using (Package = WordprocessingDocument.Open(documentStream, true))
{
GenerateInternal(model);
}
Expand All @@ -98,8 +98,43 @@ public MemoryStream Generate(object model = null)
}
#endif
// Reset position of stream.
outputstream.Seek(0, SeekOrigin.Begin);
return outputstream;
documentStream.Seek(0, SeekOrigin.Begin);
return documentStream;
}

protected Stream GetDocumentStream(string documentPath = null)
{
if (ViewPath != null && documentPath != null)
{
// Both the view and the document are files. Copy view to document.
File.Copy(ViewPath, documentPath, true);
return new FileStream(documentPath, FileMode.Open, FileAccess.ReadWrite);
}

Stream documentStream;
if (documentPath != null)
{
documentStream = new FileStream(documentPath, FileMode.Create, FileAccess.ReadWrite);
}
else
{
documentStream = new MemoryStream();
}

if (ViewPath != null)
{
// The view is a file. Copy view file to document stream (file or memory).
using var viewStream = File.OpenRead(ViewPath);
viewStream.CopyTo(documentStream);
}
else if (ViewStream != null)
{
// The view is a stream. Copy view stream to document stream (file or memory).
ViewStream.Seek(0, SeekOrigin.Begin);
ViewStream.CopyTo(documentStream);
}

return documentStream;
}

protected void GenerateInternal(object model)
Expand Down Expand Up @@ -135,9 +170,8 @@ public static List<string> GetReferencedAssemblies()
return null;
}

internal void Init(string viewPath, object model)
internal void Init(object model)
{
ViewPath = viewPath;
SetModel(model);
}

Expand Down
24 changes: 5 additions & 19 deletions SharpDocx/DocumentCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public override void SetModel(object model)
}";

internal static Assembly Compile(
string viewPath,
Stream viewStream,
string className,
string baseClassName,
Type modelType,
Expand All @@ -82,25 +82,11 @@ internal static Assembly Compile(
List<CodeBlock> codeBlocks;

#if DEBUG
// Copy the template to a temporary file, so it can be opened even when the template is open in Word.
// This makes testing templates a lot easier.
var tempFilePath = $"{Path.GetTempPath()}{Guid.NewGuid():N}.cs.docx";
File.Copy(viewPath, tempFilePath, true);

try
{
using (var package = WordprocessingDocument.Open(tempFilePath, false))
{
var codeBlockBuilder = new CodeBlockBuilder(package);
codeBlocks = codeBlockBuilder.CodeBlocks;
}
}
finally
{
File.Delete(tempFilePath);
}
using var package = WordprocessingDocument.Open(viewStream, false);
var codeBlockBuilder = new CodeBlockBuilder(package);
codeBlocks = codeBlockBuilder.CodeBlocks;
#else
using (var package = WordprocessingDocument.Open(viewPath, false))
using (var package = WordprocessingDocument.Open(viewStream, false))
{
var codeBlockBuilder = new CodeBlockBuilder(package);
codeBlocks = codeBlockBuilder.CodeBlocks;
Expand Down
71 changes: 66 additions & 5 deletions SharpDocx/DocumentFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.IO;
using System.Security.Cryptography;
#if NETSTANDARD2_0
using System.Runtime.Loader;
#endif
Expand Down Expand Up @@ -86,14 +87,66 @@ public static DocumentBase Create(
bool forceCompile = false)
{
viewPath = Path.GetFullPath(viewPath);

if (!File.Exists(viewPath))
{
throw new ArgumentException($"Could not find the file '{viewPath}'", nameof(viewPath));
}

var viewLastWriteTime = new FileInfo(viewPath).LastWriteTime.ToString("s", System.Globalization.CultureInfo.InvariantCulture);
var viewId = viewPath + viewLastWriteTime;

#if DEBUG
// Copy the template to a temporary file, so it can be opened even when the template is open in Word.
// This makes testing templates a lot easier.
var tempFilePath = $"{Path.GetTempPath()}{Guid.NewGuid():N}.cs.docx";
File.Copy(viewPath, tempFilePath, true);
using var viewStream = File.OpenRead(tempFilePath);

DocumentBase documentBase;
try
{
documentBase = CreateInternal(viewId, viewStream, model, baseClassType, forceCompile);
}
finally
{
viewStream.Close();
File.Delete(tempFilePath);
}
#else
using var viewStream = File.OpenRead(viewPath);
var documentBase = CreateInternal(viewId, viewStream, model, baseClassType, forceCompile);
#endif

documentBase.ViewPath = viewPath;
return documentBase;
}

public static TBaseClass Create<TBaseClass>(Stream viewStream, object model = null, bool forceCompile = false)
where TBaseClass : DocumentBase
{
return (TBaseClass)Create(viewStream, model, typeof(TBaseClass), forceCompile);
}

public static DocumentBase Create(
Stream viewStream,
object model = null,
Type baseClassType = null,
bool forceCompile = false)
{
var viewId = GetHash(viewStream);
var documentBase = CreateInternal(viewId, viewStream, model, baseClassType, forceCompile);
documentBase.ViewStream = viewStream;
return documentBase;
}

private static DocumentBase CreateInternal(
string viewId,
Stream viewStream,
object model = null,
Type baseClassType = null,
bool forceCompile = false)
{
if (baseClassType == null)
{
baseClassType = typeof(DocumentBase);
Expand All @@ -105,22 +158,30 @@ public static DocumentBase Create(

lock (AssembliesLock)
{
da = (DocumentAssembly)Assemblies[viewPath + viewLastWriteTime + baseClassName + modelTypeName];
da = (DocumentAssembly)Assemblies[viewId + baseClassName + modelTypeName];

if (da == null || forceCompile)
{
da = new DocumentAssembly(
viewPath,
viewStream,
baseClassType,
model?.GetType());

Assemblies[viewPath + viewLastWriteTime + baseClassName + modelTypeName] = da;
Assemblies[viewId + baseClassName + modelTypeName] = da;
}
}

var document = (DocumentBase)da.Instance();
document.Init(viewPath, model);
document.Init(model);
return document;
}

private static string GetHash(Stream s)
{
using SHA256 sha256 = SHA256.Create();
s.Seek(0, SeekOrigin.Begin);
var hashBytes = sha256.ComputeHash(s);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
}
Loading

0 comments on commit 96d0b9c

Please sign in to comment.