Adding #if DOTNET35 for REST
[castle.git] / MonoRail / Castle.MonoRail.Views.Brail / BooViewEngine.cs
blobd11f98e02e340dabb5963efed7e275efbf92adfd
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Views.Brail
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Configuration;
21 using System.IO;
22 using System.Reflection;
23 using System.Runtime.CompilerServices;
24 using System.Runtime.Serialization;
25 using System.Text;
26 using System.Threading;
27 using System.Web;
28 using Boo.Lang.Compiler;
29 using Boo.Lang.Compiler.IO;
30 using Boo.Lang.Compiler.Pipelines;
31 using Boo.Lang.Compiler.Steps;
32 using Boo.Lang.Parser;
33 using Castle.Core.Logging;
34 using Core;
35 using Framework;
37 public class BooViewEngine : ViewEngineBase, IInitializable
39 public event Action<string> ViewRecompiled = delegate { };
41 private static BooViewEngineOptions options;
43 /// <summary>
44 /// This field holds all the cache of all the
45 /// compiled types (not instances) of all the views that Brail nows of.
46 /// </summary>
47 private readonly Hashtable compilations = Hashtable.Synchronized(
48 new Hashtable(StringComparer.InvariantCultureIgnoreCase));
50 /// <summary>
51 /// used to hold the constructors of types, so we can avoid using
52 /// Activator (which takes a long time
53 /// </summary>
54 private readonly Hashtable constructors = Hashtable.Synchronized(new Hashtable());
56 /// <summary>
57 /// Used to map between type and file name, this is useful when we
58 /// want to remove a script by its type.
59 /// </summary>
60 private readonly Hashtable typeToFileName = Hashtable.Synchronized(new Hashtable());
62 private string baseSavePath;
64 /// <summary>
65 /// This is used to add a reference to the common scripts for each compiled scripts
66 /// </summary>
67 private Assembly common;
69 private ILogger logger;
71 public override bool SupportsJSGeneration
73 get { return true; }
76 public override string ViewFileExtension
78 get { return ".brail"; }
81 public override string JSGeneratorFileExtension
83 get { return ".brailjs"; }
86 public string ViewRootDir
88 get { return ViewSourceLoader.ViewRootDir; }
91 public BooViewEngineOptions Options
93 get { return options; }
94 set { options = value; }
97 #region IInitializable Members
99 public void Initialize()
101 if (options == null)
103 InitializeConfig();
106 string baseDir = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
107 Log("Base Directory: " + baseDir);
108 baseSavePath = Path.Combine(baseDir, options.SaveDirectory);
109 Log("Base Save Path: " + baseSavePath);
111 if (options.SaveToDisk && !Directory.Exists(baseSavePath))
113 Directory.CreateDirectory(baseSavePath);
114 Log("Created directory " + baseSavePath);
117 CompileCommonScripts();
119 ViewSourceLoader.ViewChanged += OnViewChanged;
122 #endregion
124 // Process a template name and output the results to the user
125 // This may throw if an error occured and the user is not local (which would
126 // cause the yellow screen of death)
127 public override void Process(String templateName, TextWriter output, IEngineContext context, IController controller,
128 IControllerContext controllerContext)
130 Log("Starting to process request for {0}", templateName);
131 string file = templateName + ViewFileExtension;
132 BrailBase view;
133 // Output may be the layout's child output if a layout exists
134 // or the context.Response.Output if the layout is null
135 LayoutViewOutput layoutViewOutput = GetOutput(output, context, controller, controllerContext);
136 // Will compile on first time, then save the assembly on the cache.
137 view = GetCompiledScriptInstance(file, layoutViewOutput.Output, context, controller, controllerContext);
138 if (controller != null)
140 controller.PreSendView(view);
143 Log("Executing view {0}", templateName);
147 view.Run();
149 catch(Exception e)
151 HandleException(templateName, view, e);
154 if (layoutViewOutput.Layout != null)
156 layoutViewOutput.Layout.SetParent(view);
160 layoutViewOutput.Layout.Run();
162 catch(Exception e)
164 HandleException(controllerContext.LayoutNames[0], layoutViewOutput.Layout, e);
167 Log("Finished executing view {0}", templateName);
168 if (controller != null)
170 controller.PostSendView(view);
174 public override void Process(string templateName, string layoutName, TextWriter output,
175 IDictionary<string, object> parameters)
177 ControllerContext controllerContext = new ControllerContext();
178 if (layoutName != null)
180 controllerContext.LayoutNames = new string[] {layoutName};
182 foreach(KeyValuePair<string, object> pair in parameters)
184 controllerContext.PropertyBag[pair.Key] = pair.Value;
187 Process(templateName, output, null, null, controllerContext);
190 public override void ProcessPartial(string partialName, TextWriter output, IEngineContext context,
191 IController controller, IControllerContext controllerContext)
193 Log("Generating partial for {0}", partialName);
197 string file = ResolveTemplateName(partialName);
198 BrailBase view = GetCompiledScriptInstance(file, output, context, controller, controllerContext);
199 Log("Executing partial view {0}", partialName);
200 view.Run();
201 Log("Finished executing partial view {0}", partialName);
203 catch(Exception ex)
205 if (Logger != null && Logger.IsErrorEnabled)
207 Logger.Error("Could not generate JS", ex);
210 throw new MonoRailException("Error generating partial: " + partialName, ex);
214 public override object CreateJSGenerator(JSCodeGeneratorInfo generatorInfo, IEngineContext context,
215 IController controller,
216 IControllerContext controllerContext)
218 return new BrailJSGenerator(generatorInfo.CodeGenerator, generatorInfo.LibraryGenerator,
219 generatorInfo.Extensions, generatorInfo.ElementExtensions);
222 public override void GenerateJS(string templateName, TextWriter output, JSCodeGeneratorInfo generatorInfo,
223 IEngineContext context, IController controller, IControllerContext controllerContext)
225 Log("Generating JS for {0}", templateName);
229 object generator = CreateJSGenerator(generatorInfo, context, controller, controllerContext);
230 AdjustJavascriptContentType(context);
231 string file = ResolveJSTemplateName(templateName);
232 BrailBase view = GetCompiledScriptInstance(file,
233 //we use the script just to build the generator, not to output to the user
234 new StringWriter(),
235 context, controller, controllerContext);
236 Log("Executing JS view {0}", templateName);
237 view.AddProperty("page", generator);
238 view.Run();
240 output.WriteLine(generator);
241 Log("Finished executing JS view {0}", templateName);
243 catch(Exception ex)
245 if (Logger != null && Logger.IsErrorEnabled)
247 Logger.Error("Could not generate JS", ex);
250 throw new MonoRailException("Error generating JS. Template: " + templateName, ex);
254 /// <summary>
255 /// Wraps the specified content in the layout using the
256 /// context to output the result.
257 /// </summary>
258 /// <param name="contents"></param>
259 /// <param name="context"></param>
260 /// <param name="controller"></param>
261 /// <param name="controllerContext"></param>
262 public override void RenderStaticWithinLayout(String contents, IEngineContext context, IController controller,
263 IControllerContext controllerContext)
265 LayoutViewOutput layoutViewOutput = GetOutput(context.Response.Output, context, controller, controllerContext);
266 layoutViewOutput.Output.Write(contents);
267 // here we don't need to pass parameters from the layout to the view,
268 if (layoutViewOutput.Layout != null)
270 layoutViewOutput.Layout.Run();
274 private void HandleException(string templateName, BrailBase view, Exception e)
276 StringBuilder sb = new StringBuilder();
277 sb.Append("Exception on process view: ").AppendLine(templateName);
278 sb.Append("Last accessed variable: ").Append(view.LastVariableAccessed);
279 string msg = sb.ToString();
280 sb.Append("Exception: ").AppendLine(e.ToString());
281 Log(msg);
282 throw new MonoRailException(msg, e);
285 private void OnViewChanged(object sender, FileSystemEventArgs e)
287 if (Path.GetExtension(e.FullPath).IndexOf(this.ViewFileExtension) == -1 &&
288 Path.GetExtension(e.FullPath).IndexOf(this.JSGeneratorFileExtension) == -1 &&
289 Path.GetExtension(e.FullPath).IndexOf(".boo") == -1)
291 return; //early return since only watching view extensions and jsgenerator extensions
294 string viewRoot = String.Empty;
295 IViewSourceLoader loaderThatDetectedTheChange = sender as IViewSourceLoader;
296 if(loaderThatDetectedTheChange == null)
298 viewRoot = ViewRootDir;
300 else
302 viewRoot = loaderThatDetectedTheChange.ViewRootDir;
305 string path = e.FullPath.Substring(viewRoot.Length);
306 path = EnsurePathDoesNotStartWithDirectorySeparator(path);
307 if (path.IndexOf(options.CommonScriptsDirectory) != -1)
309 Log("Detected a change in commons scripts directory " + options.CommonScriptsDirectory + ", recompiling site");
310 // need to invalidate the entire CommonScripts assembly
311 // not worrying about concurrency here, since it is assumed
312 // that changes here are rare. Note, this force a recompile of the
313 // whole site!
316 WaitForFileToBecomeAvailableForReading(e);
317 CompileCommonScripts();
319 catch(Exception ex)
321 // we failed to recompile the commons scripts directory, but because we are running
322 // on another thread here, and exception would kill the application, so we log it
323 // and continue on. CompileCommonScripts() will only change the global state if it has
324 // successfully compiled the commons scripts directory.
325 Log("Failed to recompile the commons scripts directory! {0}", ex);
328 else
330 Log("Detected a change in {0}, removing from complied cache", e.Name);
331 // Will cause a recompilation
332 compilations[path] = null;
334 ViewRecompiled(path);
337 private string EnsurePathDoesNotStartWithDirectorySeparator(string path)
339 if (path.Length > 0 && (path[0] == Path.DirectorySeparatorChar ||
340 path[0] == Path.AltDirectorySeparatorChar))
342 path = path.Substring(1);
344 return path;
347 private static void WaitForFileToBecomeAvailableForReading(FileSystemEventArgs e)
349 // We may need to wait while the file is being written and closed to disk
350 int retries = 10;
351 bool successfullyOpenedFile = false;
352 while(retries != 0 && successfullyOpenedFile == false)
354 retries -= 1;
357 using(File.OpenRead(e.FullPath))
359 successfullyOpenedFile = true;
362 catch(IOException)
364 //The file is probably in locked because it is currently being written to,
365 // will wait a while for it to be freed.
366 // again, this isn't something that need to be very robust, it runs on a separate thread
367 // and if it fails, it is not going to do any damage
368 Thread.Sleep(250);
373 public void SetViewSourceLoader(IViewSourceLoader loader)
375 ViewSourceLoader = loader;
378 // Get configuration options if they exists, if they do not exist, load the default ones
379 // Create directory to save the compiled assemblies if required.
380 // pre-compile the common scripts
381 public override void Service(IServiceProvider serviceProvider)
383 base.Service(serviceProvider);
384 ILoggerFactory loggerFactory = serviceProvider.GetService(typeof(ILoggerFactory)) as ILoggerFactory;
385 if (loggerFactory != null)
387 logger = loggerFactory.Create(GetType());
391 // Check if a layout has been defined. If it was, then the layout would be created
392 // and will take over the output, otherwise, the context.Reposne.Output is used,
393 // and layout is null
394 private LayoutViewOutput GetOutput(TextWriter output, IEngineContext context, IController controller,
395 IControllerContext controllerContext)
397 BrailBase layout = null;
398 if (controllerContext.LayoutNames != null && controllerContext.LayoutNames.Length != 0)
400 string layoutTemplate = controllerContext.LayoutNames[0];
401 if (layoutTemplate.StartsWith("/") == false)
403 layoutTemplate = "layouts\\" + layoutTemplate;
405 string layoutFilename = layoutTemplate + ViewFileExtension;
406 layout = GetCompiledScriptInstance(layoutFilename, output,
407 context, controller, controllerContext);
408 output = layout.ChildOutput = new StringWriter();
410 return new LayoutViewOutput(output, layout);
413 /// <summary>
414 /// This takes a filename and return an instance of the view ready to be used.
415 /// If the file does not exist, an exception is raised
416 /// The cache is checked to see if the file has already been compiled, and it had been
417 /// a check is made to see that the compiled instance is newer then the file's modification date.
418 /// If the file has not been compiled, or the version on disk is newer than the one in memory, a new
419 /// version is compiled.
420 /// Finally, an instance is created and returned
421 /// </summary>
422 public BrailBase GetCompiledScriptInstance(
423 string file,
424 TextWriter output,
425 IEngineContext context,
426 IController controller, IControllerContext controllerContext)
428 bool batch = options.BatchCompile;
430 // normalize filename - replace / or \ to the system path seperator
431 string filename = file.Replace('/', Path.DirectorySeparatorChar)
432 .Replace('\\', Path.DirectorySeparatorChar);
434 filename = EnsurePathDoesNotStartWithDirectorySeparator(filename);
435 Log("Getting compiled instnace of {0}", filename);
437 Type type;
439 if (compilations.ContainsKey(filename))
441 type = (Type) compilations[filename];
442 if (type != null)
444 Log("Got compiled instance of {0} from cache", filename);
445 return CreateBrailBase(context, controller, controllerContext, output, type);
447 // if file is in compilations and the type is null,
448 // this means that we need to recompile. Since this usually means that
449 // the file was changed, we'll set batch to false and procceed to compile just
450 // this file.
451 Log("Cache miss! Need to recompile {0}", filename);
452 batch = false;
455 type = CompileScript(filename, batch);
457 if (type == null)
459 throw new MonoRailException("Could not find a view with path " + filename);
462 return CreateBrailBase(context, controller, controllerContext, output, type);
465 private BrailBase CreateBrailBase(IEngineContext context, IController controller, IControllerContext controllerContext,
466 TextWriter output, Type type)
468 ConstructorInfo constructor = (ConstructorInfo) constructors[type];
469 if(constructor==null)
471 string message = "Could not find a constructor for "+type+", but it was found in the compilation cache. Clearing the compilation cache for the type, please try again";
472 Log(message);
474 object key = this.typeToFileName[type];
475 if(key!=null)
477 compilations.Remove(key);
480 throw new MonoRailException(message);
482 BrailBase self = (BrailBase) FormatterServices.GetUninitializedObject(type);
483 constructor.Invoke(self, new object[] {this, output, context, controller, controllerContext});
484 return self;
487 // Compile a script (or all scripts in a directory), save the compiled result
488 // to the cache and return the compiled type.
489 // If an error occurs in batch compilation, then an attempt is made to compile just the single
490 // request file.
491 [MethodImpl(MethodImplOptions.Synchronized)]
492 public Type CompileScript(string filename, bool batch)
494 IDictionary<ICompilerInput, string> inputs2FileName = GetInput(filename, batch);
495 string name = NormalizeName(filename);
496 Log("Compiling {0} to {1} with batch: {2}", filename, name, batch);
497 CompilationResult result = DoCompile(inputs2FileName.Keys, name);
499 if (result.Context.Errors.Count > 0)
501 if (batch == false)
503 RaiseCompilationException(filename, inputs2FileName, result);
505 //error compiling a batch, let's try a single file
506 return CompileScript(filename, false);
508 Type type;
509 foreach(ICompilerInput input in inputs2FileName.Keys)
511 string viewName = Path.GetFileNameWithoutExtension(input.Name);
512 string typeName = TransformToBrailStep.GetViewTypeName(viewName);
513 type = result.Context.GeneratedAssembly.GetType(typeName);
514 Log("Adding {0} to the cache", type.FullName);
515 constructors[type] = type.GetConstructor(new Type[]
517 typeof(BooViewEngine),
518 typeof(TextWriter),
519 typeof(IEngineContext),
520 typeof(IController),
521 typeof(IControllerContext)
523 string compilationName = inputs2FileName[input];
524 typeToFileName[type] = compilationName;
525 compilations[compilationName] = type;
527 type = (Type) compilations[filename];
528 return type;
531 private void RaiseCompilationException(string filename, IDictionary<ICompilerInput, string> inputs2FileName,
532 CompilationResult result)
534 string errors = result.Context.Errors.ToString(true);
535 Log("Failed to compile {0} because {1}", filename, errors);
536 StringBuilder code = new StringBuilder();
537 foreach(ICompilerInput input in inputs2FileName.Keys)
539 code.AppendLine()
540 .Append(result.Processor.GetInputCode(input))
541 .AppendLine();
543 throw new HttpParseException("Error compiling Brail code",
544 result.Context.Errors[0],
545 filename,
546 code.ToString(), result.Context.Errors[0].LexicalInfo.Line);
549 // If batch compilation is set to true, this would return all the view scripts
550 // in the director (not recursive!)
551 // Otherwise, it would return just the single file
552 private IDictionary<ICompilerInput, string> GetInput(string filename, bool batch)
554 Dictionary<ICompilerInput, string> input2FileName = new Dictionary<ICompilerInput, string>();
555 if (batch == false)
557 input2FileName.Add(CreateInput(filename), filename);
558 return input2FileName;
560 // use the System.IO.Path to get the folder name even though
561 // we are using the ViewSourceLoader to load the actual file
562 string directory = Path.GetDirectoryName(filename);
563 foreach(string file in ViewSourceLoader.ListViews(directory, this.ViewFileExtension, this.JSGeneratorFileExtension))
565 ICompilerInput input = CreateInput(file);
566 input2FileName.Add(input, file);
568 return input2FileName;
571 // create an input from a resource name
572 public ICompilerInput CreateInput(string name)
574 IViewSource viewSrc = ViewSourceLoader.GetViewSource(name);
575 if (viewSrc == null)
577 throw new MonoRailException("{0} is not a valid view", name);
579 // I need to do it this way because I can't tell
580 // when to dispose of the stream.
581 // It is not expected that this will be a big problem, the string
582 // will go away after the compile is done with them.
583 using(StreamReader stream = new StreamReader(viewSrc.OpenViewStream()))
585 return new StringInput(name, stream.ReadToEnd());
589 /// <summary>
590 /// Perform the actual compilation of the scripts
591 /// Things to note here:
592 /// * The generated assembly reference the Castle.MonoRail.MonoRailBrail and Castle.MonoRail.Framework assemblies
593 /// * If a common scripts assembly exist, it is also referenced
594 /// * The AddBrailBaseClassStep compiler step is added - to create a class from the view's code
595 /// * The ProcessMethodBodiesWithDuckTyping is replaced with ReplaceUknownWithParameters
596 /// this allows to use naked parameters such as (output context.IsLocal) without using
597 /// any special syntax
598 /// * The FixTryGetParameterConditionalChecks is run afterward, to transform "if ?Error" to "if not ?Error isa IgnoreNull"
599 /// * The ExpandDuckTypedExpressions is replace with a derived step that allows the use of Dynamic Proxy assemblies
600 /// * The IntroduceGlobalNamespaces step is removed, to allow to use common variables such as
601 /// date and list without accidently using the Boo.Lang.BuiltIn versions
602 /// </summary>
603 /// <param name="files"></param>
604 /// <param name="name"></param>
605 /// <returns></returns>
606 private CompilationResult DoCompile(IEnumerable<ICompilerInput> files, string name)
608 ICompilerInput[] filesAsArray = new List<ICompilerInput>(files).ToArray();
609 BooCompiler compiler = SetupCompiler(filesAsArray);
610 string filename = Path.Combine(baseSavePath, name);
611 compiler.Parameters.OutputAssembly = filename;
612 // this is here and not in SetupCompiler since CompileCommon is also
613 // using SetupCompiler, and we don't want reference to the old common from the new one
614 if (common != null)
616 compiler.Parameters.References.Add(common);
618 // pre procsssor needs to run before the parser
619 BrailPreProcessor processor = new BrailPreProcessor(this);
620 compiler.Parameters.Pipeline.Insert(0, processor);
621 // inserting the add class step after the parser
622 compiler.Parameters.Pipeline.Insert(2, new TransformToBrailStep(options));
623 compiler.Parameters.Pipeline.Replace(typeof(ProcessMethodBodiesWithDuckTyping),
624 new ReplaceUknownWithParameters());
625 compiler.Parameters.Pipeline.Replace(typeof(ExpandDuckTypedExpressions),
626 new ExpandDuckTypedExpressions_WorkaroundForDuplicateVirtualMethods());
627 compiler.Parameters.Pipeline.Replace(typeof(InitializeTypeSystemServices),
628 new InitializeCustomTypeSystem());
629 compiler.Parameters.Pipeline.InsertBefore(typeof(ReplaceUknownWithParameters),
630 new FixTryGetParameterConditionalChecks());
631 compiler.Parameters.Pipeline.RemoveAt(compiler.Parameters.Pipeline.Find(typeof(IntroduceGlobalNamespaces)));
633 return new CompilationResult(compiler.Run(), processor);
636 // Return the output filename for the generated assembly
637 // The filename is dependant on whatever we are doing a batch
638 // compile or not, if it's a batch compile, then the directory name
639 // is used, if it's just a single file, we're using the file's name.
640 // '/' and '\' are replaced with '_', I'm not handling ':' since the path
641 // should never include it since I'm converting this to a relative path
642 public string NormalizeName(string filename)
644 string name = filename;
645 name = name.Replace(Path.AltDirectorySeparatorChar, '_');
646 name = name.Replace(Path.DirectorySeparatorChar, '_');
648 return name + "_BrailView.dll";
651 // Compile all the common scripts to a common assemblies
652 // an error in the common scripts would raise an exception.
653 public bool CompileCommonScripts()
655 if (options.CommonScriptsDirectory == null)
657 return false;
660 // the demi.boo is stripped, but GetInput require it.
661 string demiFile = Path.Combine(options.CommonScriptsDirectory, "demi.brail");
662 IDictionary<ICompilerInput, string> inputs = GetInput(demiFile, true);
663 ICompilerInput[] inputsAsArray = new List<ICompilerInput>(inputs.Keys).ToArray();
664 BooCompiler compiler = SetupCompiler(inputsAsArray);
665 string outputFile = Path.Combine(baseSavePath, "CommonScripts.dll");
666 compiler.Parameters.OutputAssembly = outputFile;
667 CompilerContext result = compiler.Run();
668 if (result.Errors.Count > 0)
670 throw new MonoRailException(result.Errors.ToString(true));
672 common = result.GeneratedAssembly;
673 compilations.Clear();
674 return true;
677 // common setup for the compiler
678 private static BooCompiler SetupCompiler(IEnumerable<ICompilerInput> files)
680 BooCompiler compiler = new BooCompiler();
681 compiler.Parameters.Ducky = true;
682 compiler.Parameters.Debug = options.Debug;
683 if (options.SaveToDisk)
685 compiler.Parameters.Pipeline = new CompileToFile();
687 else
689 compiler.Parameters.Pipeline = new CompileToMemory();
691 // replace the normal parser with white space agnostic one.
692 compiler.Parameters.Pipeline.RemoveAt(0);
693 compiler.Parameters.Pipeline.Insert(0, new WSABooParsingStep());
694 foreach(ICompilerInput file in files)
696 compiler.Parameters.Input.Add(file);
698 foreach(Assembly assembly in options.AssembliesToReference)
700 compiler.Parameters.References.Add(assembly);
702 compiler.Parameters.OutputType = CompilerOutputType.Library;
703 return compiler;
706 private static void InitializeConfig()
708 InitializeConfig("brail");
710 if (options == null)
712 InitializeConfig("Brail");
715 if (options == null)
717 options = new BooViewEngineOptions();
721 private static void InitializeConfig(string sectionName)
723 options = ConfigurationManager.GetSection(sectionName) as BooViewEngineOptions;
726 private void Log(string msg, params object[] items)
728 if (logger == null || logger.IsDebugEnabled == false)
730 return;
732 logger.DebugFormat(msg, items);
735 public bool ConditionalPreProcessingOnly(string name)
737 return String.Equals(
738 Path.GetExtension(name),
739 JSGeneratorFileExtension,
740 StringComparison.InvariantCultureIgnoreCase);
743 #region Nested type: CompilationResult
745 private class CompilationResult
747 private readonly CompilerContext context;
748 private readonly BrailPreProcessor processor;
750 public CompilationResult(CompilerContext context, BrailPreProcessor processor)
752 this.context = context;
753 this.processor = processor;
756 public CompilerContext Context
758 get { return context; }
761 public BrailPreProcessor Processor
763 get { return processor; }
767 #endregion
769 #region Nested type: LayoutViewOutput
771 private class LayoutViewOutput
773 private readonly BrailBase layout;
774 private readonly TextWriter output;
776 public LayoutViewOutput(TextWriter output, BrailBase layout)
778 this.layout = layout;
779 this.output = output;
782 public BrailBase Layout
784 get { return layout; }
787 public TextWriter Output
789 get { return output; }
793 #endregion