1 // Copyright 2004-2007 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 using NVelocity
.Runtime
.Parser
.Node
;
16 using NVelocity
.Runtime
.Resource
;
17 using NVelocity
.Runtime
.Directive
;
18 using NVelocity
.Runtime
;
19 using NVelocity
.Exception
;
20 using NVelocity
.Runtime
.Parser
;
21 using IInternalContextAdapter
= NVelocity
.Context
.IInternalContextAdapter
;
22 using Template
= NVelocity
.Template
;
24 namespace Castle
.MonoRail
.Framework
.Views
.NVelocity
.CustomDirectives
27 using System
.Collections
.Specialized
;
30 using System
.Collections
;
32 using Castle
.MonoRail
.Framework
;
37 public abstract class AbstractComponentDirective
: Directive
, IViewRenderer
39 private readonly IViewComponentFactory viewComponentFactory
;
41 private String componentName
;
42 private ViewComponent component
;
43 private NVelocityViewContextAdapter contextAdapter
;
44 private IViewEngine viewEngine
;
45 private INode compNameNode
;
48 /// Initializes a new instance of the <see cref="AbstractComponentDirective"/> class.
50 /// <param name="viewComponentFactory">The view component factory.</param>
51 /// <param name="viewEngine">The view engine instance</param>
52 public AbstractComponentDirective(IViewComponentFactory viewComponentFactory
, IViewEngine viewEngine
)
54 this.viewComponentFactory
= viewComponentFactory
;
55 this.viewEngine
= viewEngine
;
58 public override void Init(IRuntimeServices rs
, IInternalContextAdapter context
, INode node
)
60 base.Init(rs
, context
, node
);
62 compNameNode
= node
.GetChild(0);
64 if (compNameNode
== null)
66 String message
= String
.Format("You must specify the component name on the #{0} directive", Name
);
67 throw new ViewComponentException(message
);
71 public override bool Render(IInternalContextAdapter context
, TextWriter writer
, INode node
)
73 componentName
= compNameNode
.FirstToken
.Image
;
75 if (componentName
== null)
77 String message
= String
.Format("Could not obtain component name from the #{0} directive", Name
);
78 throw new ViewComponentException(message
);
81 if (componentName
.StartsWith("$"))
83 String nodeContent
= compNameNode
.Literal
.Trim('"', '\'');
84 SimpleNode inlineNode
= rsvc
.Parse(new StringReader(nodeContent
), context
.CurrentTemplateName
, false);
86 inlineNode
.Init(context
, rsvc
);
87 componentName
= (String
) Evaluate(inlineNode
, context
);
90 component
= viewComponentFactory
.Create(componentName
);
92 ASTDirective directiveNode
= (ASTDirective
) node
;
93 IViewRenderer renderer
= (IViewRenderer
) directiveNode
.Directive
;
95 contextAdapter
= new NVelocityViewContextAdapter(componentName
, node
, viewEngine
, renderer
);
96 contextAdapter
.Context
= context
;
100 INode bodyNode
= null;
102 IDictionary componentParams
= CreateParameters(context
, node
);
104 if (node
.ChildrenCount
> 0)
106 bodyNode
= node
.GetChild(node
.ChildrenCount
- 1);
109 contextAdapter
.BodyNode
= bodyNode
;
110 contextAdapter
.ComponentParams
= componentParams
;
111 contextAdapter
.TextWriter
= writer
;
113 IRailsEngineContext railsContext
= MonoRailHttpHandler
.CurrentContext
;
115 const string ViewComponentContextKey
= "viewcomponent";
119 contextAdapter
.ContextVars
[ViewComponentContextKey
] = component
;
121 component
.Init(railsContext
, contextAdapter
);
125 if (contextAdapter
.ViewToRender
!= null)
127 return RenderComponentView(context
, contextAdapter
.ViewToRender
, writer
, contextAdapter
);
132 contextAdapter
.ContextVars
.Remove(ViewComponentContextKey
);
138 public bool RenderComponentView(IInternalContextAdapter context
, String viewToRender
, TextWriter writer
, NVelocityViewContextAdapter contextAdapter
)
140 foreach(DictionaryEntry entry
in contextAdapter
.ContextVars
)
142 context
.Put(entry
.Key
.ToString(), entry
.Value
);
145 viewToRender
= viewToRender
+ NVelocityViewEngine
.TemplateExtension
;
147 CheckTemplateStack(context
);
149 String encoding
= SetUpEncoding(context
);
151 Template template
= GetTemplate(viewToRender
, encoding
);
153 return RenderView(context
, viewToRender
, template
, writer
);
156 protected virtual void ProcessSubSections()
160 protected virtual IDictionary
CreateParameters(IInternalContextAdapter context
, INode node
)
162 int childrenCount
= node
.ChildrenCount
;
164 if (childrenCount
> 1)
166 INode lastNode
= node
.GetChild(childrenCount
- 1);
168 if (lastNode
.Type
== ParserTreeConstants
.BLOCK
)
173 if (childrenCount
> 1)
175 IDictionary dict
= ProcessFirstParam(node
, context
, childrenCount
);
181 else if (childrenCount
> 2)
183 return ProcessRemainingParams(childrenCount
, node
, context
);
188 return new Hashtable(0);
191 protected string ComponentName
193 get { return componentName; }
196 protected ViewComponent Component
198 get { return component; }
201 protected NVelocityViewContextAdapter ContextAdapter
203 get { return contextAdapter; }
206 private IDictionary
ProcessRemainingParams(int childrenCount
, INode node
, IInternalContextAdapter context
)
208 IDictionary entries
= new HybridDictionary(true);
210 for(int i
= 2; i
< childrenCount
; i
++)
212 INode paramNode
= node
.GetChild(i
);
214 string nodeContent
= paramNode
.Literal
.TrimStart('"', '\'').TrimEnd('"', '\'');
216 string[] parts
= nodeContent
.Split(new char[] {'='}
, 2, StringSplitOptions
.RemoveEmptyEntries
);
218 if (parts
.Length
== 2 && parts
[1].IndexOf("$") != -1)
220 SimpleNode inlineNode
= rsvc
.Parse(new StringReader(parts
[1]), context
.CurrentTemplateName
, false);
222 inlineNode
.Init(context
, rsvc
);
224 entries
[parts
[0]] = Evaluate(inlineNode
, context
);
226 else if (parts
.Length
== 2)
228 entries
[parts
[0]] = parts
[1];
232 entries
[parts
[0]] = String
.Empty
;
239 private object Evaluate(SimpleNode inlineNode
, IInternalContextAdapter context
)
241 ArrayList values
= new ArrayList();
243 for(int i
=0; i
< inlineNode
.ChildrenCount
; i
++)
245 INode node
= inlineNode
.GetChild(i
);
247 if (node
.Type
== ParserTreeConstants
.TEXT
)
249 values
.Add(((ASTText
)node
).Text
);
253 values
.Add(node
.Value(context
));
257 if (values
.Count
== 0)
261 else if (values
.Count
== 1)
267 StringBuilder sb
= new StringBuilder();
268 foreach(object value in values
)
272 return sb
.ToString();
277 /// Processes the first param.
278 /// first param can either be the literal string 'with' which means the user
279 /// is using the syntax #blockcomponent(ComponentName with "param1=value1" "param2=value2")
280 /// or it could be a dictionary string like:
281 /// #blockcomponent(ComponentName "#{ param1='value1', param2='value2' }")
282 /// anything different than that will throw an exception
284 /// <param name="node">The node.</param>
285 /// <param name="context">The context.</param>
286 /// <param name="childrenCount">The children count.</param>
287 /// <returns></returns>
288 private IDictionary
ProcessFirstParam(INode node
, IInternalContextAdapter context
, int childrenCount
)
290 INode withNode
= node
.GetChild(1);
292 String withName
= withNode
.FirstToken
.Image
;
294 if (!"with".Equals(withName
))
296 IDictionary dict
= withNode
.Value(context
) as IDictionary
;
300 if (childrenCount
> 2)
302 String message
= String
.Format("A #{0} directive with a dictionary " +
303 "string param cannot have extra params - component {0}", componentName
, Name
);
304 throw new ViewComponentException(message
);
310 String message
= String
.Format("A #{0} directive with parameters must use " +
311 "the keyword 'with' - component {0}", componentName
, Name
);
312 throw new ViewComponentException(message
);
319 private bool RenderView(IInternalContextAdapter context
,
320 String viewToRender
, Template template
, TextWriter writer
)
324 context
.PushCurrentTemplateName(viewToRender
);
325 ((SimpleNode
) template
.Data
).Render(context
, writer
);
329 if (e
is MethodInvocationException
)
338 context
.PopCurrentTemplateName();
344 private Template
GetTemplate(String viewToRender
, String encoding
)
348 return rsvc
.GetTemplate(viewToRender
, encoding
);
356 private String
SetUpEncoding(IInternalContextAdapter context
)
358 Resource current
= context
.CurrentResource
;
360 String encoding
= null;
364 encoding
= current
.Encoding
;
368 encoding
= (String
) rsvc
.GetProperty(RuntimeConstants
.INPUT_ENCODING
);
373 private void CheckTemplateStack(IInternalContextAdapter context
)
375 Object
[] templateStack
= context
.TemplateNameStack
;
377 if (templateStack
.Length
>= rsvc
.GetInt(RuntimeConstants
.PARSE_DIRECTIVE_MAXDEPTH
, 20))
379 StringBuilder path
= new StringBuilder();
381 for(int i
= 0; i
< templateStack
.Length
; ++i
)
383 path
.Append(" > " + templateStack
[i
]);
386 throw new Exception("Max recursion depth reached (" + templateStack
.Length
+ ")" + " File stack:" + path
);