1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
;
19 using System
.Collections
.Generic
;
20 using System
.Configuration
;
22 using System
.Reflection
;
23 using System
.Runtime
.CompilerServices
;
24 using System
.Runtime
.Serialization
;
26 using System
.Threading
;
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
;
37 public class BooViewEngine
: ViewEngineBase
, IInitializable
39 public event Action
<string> ViewRecompiled
= delegate { }
;
41 private static BooViewEngineOptions options
;
44 /// This field holds all the cache of all the
45 /// compiled types (not instances) of all the views that Brail nows of.
47 private readonly Hashtable compilations
= Hashtable
.Synchronized(
48 new Hashtable(StringComparer
.InvariantCultureIgnoreCase
));
51 /// used to hold the constructors of types, so we can avoid using
52 /// Activator (which takes a long time
54 private readonly Hashtable constructors
= Hashtable
.Synchronized(new Hashtable());
57 /// Used to map between type and file name, this is useful when we
58 /// want to remove a script by its type.
60 private readonly Hashtable typeToFileName
= Hashtable
.Synchronized(new Hashtable());
62 private string baseSavePath
;
65 /// This is used to add a reference to the common scripts for each compiled scripts
67 private Assembly common
;
69 private ILogger logger
;
71 public override bool SupportsJSGeneration
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()
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
;
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
;
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
);
151 HandleException(templateName
, view
, e
);
154 if (layoutViewOutput
.Layout
!= null)
156 layoutViewOutput
.Layout
.SetParent(view
);
160 layoutViewOutput
.Layout
.Run();
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
);
201 Log("Finished executing partial view {0}", partialName
);
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
235 context
, controller
, controllerContext
);
236 Log("Executing JS view {0}", templateName
);
237 view
.AddProperty("page", generator
);
240 output
.WriteLine(generator
);
241 Log("Finished executing JS view {0}", templateName
);
245 if (Logger
!= null && Logger
.IsErrorEnabled
)
247 Logger
.Error("Could not generate JS", ex
);
250 throw new MonoRailException("Error generating JS. Template: " + templateName
, ex
);
255 /// Wraps the specified content in the layout using the
256 /// context to output the result.
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());
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
;
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
316 WaitForFileToBecomeAvailableForReading(e
);
317 CompileCommonScripts();
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
);
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);
347 private static void WaitForFileToBecomeAvailableForReading(FileSystemEventArgs e
)
349 // We may need to wait while the file is being written and closed to disk
351 bool successfullyOpenedFile
= false;
352 while(retries
!= 0 && successfullyOpenedFile
== false)
357 using(File
.OpenRead(e
.FullPath
))
359 successfullyOpenedFile
= true;
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
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
);
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
422 public BrailBase
GetCompiledScriptInstance(
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
);
439 if (compilations
.ContainsKey(filename
))
441 type
= (Type
) compilations
[filename
];
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
451 Log("Cache miss! Need to recompile {0}", filename
);
455 type
= CompileScript(filename
, batch
);
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";
474 object key
= this.typeToFileName
[type
];
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}
);
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
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)
503 RaiseCompilationException(filename
, inputs2FileName
, result
);
505 //error compiling a batch, let's try a single file
506 return CompileScript(filename
, false);
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
),
519 typeof(IEngineContext
),
521 typeof(IControllerContext
)
523 string compilationName
= inputs2FileName
[input
];
524 typeToFileName
[type
] = compilationName
;
525 compilations
[compilationName
] = type
;
527 type
= (Type
) compilations
[filename
];
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
)
540 .Append(result
.Processor
.GetInputCode(input
))
543 throw new HttpParseException("Error compiling Brail code",
544 result
.Context
.Errors
[0],
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>();
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
);
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());
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
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
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)
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();
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();
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
;
706 private static void InitializeConfig()
708 InitializeConfig("brail");
712 InitializeConfig("Brail");
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)
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; }
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; }