Fixing an issue with output parameters that are of type IntPtr
[castle.git] / MonoRail / Castle.MonoRail.Framework / JSGeneration / JSCodeGenerator.cs
blob79f88a648d6b20de77c2fb68f81dfb790d6546ff
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.Framework.JSGeneration
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.IO;
21 using System.Text;
22 using System.Text.RegularExpressions;
23 using Helpers;
24 using Services;
26 /// <summary>
27 /// Pendent
28 /// </summary>
29 public class JSCodeGenerator : IJSCodeGenerator
31 private Dictionary<string, object> extensions =
32 new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
34 private StringBuilder lines = new StringBuilder();
35 private IJSGenerator generator;
36 private IServerUtility serverUtility;
37 private IViewEngineManager viewEngineManager;
38 private IEngineContext engineContext;
39 private IController controller;
40 private IControllerContext context;
41 private IUrlBuilder urlBuilder;
43 /// <summary>
44 /// Initializes a new instance of the <see cref="JSCodeGenerator"/> class.
45 /// </summary>
46 public JSCodeGenerator()
50 /// <summary>
51 /// Initializes a new instance of the <see cref="JSCodeGenerator"/> class.
52 /// </summary>
53 /// <param name="serverUtility">The server utility.</param>
54 /// <param name="viewEngineManager">The view engine manager.</param>
55 /// <param name="engineContext">The engine context.</param>
56 /// <param name="controller">The controller.</param>
57 /// <param name="context">The context.</param>
58 /// <param name="urlBuilder">The URL builder.</param>
59 public JSCodeGenerator(IServerUtility serverUtility, IViewEngineManager viewEngineManager,
60 IEngineContext engineContext, IController controller, IControllerContext context,
61 IUrlBuilder urlBuilder)
63 this.serverUtility = serverUtility;
64 this.viewEngineManager = viewEngineManager;
65 this.engineContext = engineContext;
66 this.controller = controller;
67 this.context = context;
68 this.urlBuilder = urlBuilder;
71 /// <summary>
72 /// Gets the main JS generator.
73 /// </summary>
74 /// <value>The JS generator.</value>
75 public IJSGenerator JSGenerator
77 get { return generator; }
78 set { generator = value; }
81 /// <summary>
82 /// Gets or sets the URL helper.
83 /// </summary>
84 /// <value>The URL helper.</value>
85 public IUrlBuilder UrlBuilder
87 get { return urlBuilder; }
88 set { urlBuilder = value; }
91 /// <summary>
92 /// Gets or sets the server utility.
93 /// </summary>
94 /// <value>The server utility.</value>
95 public IServerUtility ServerUtility
97 get { return serverUtility; }
98 set { serverUtility = value; }
101 /// <summary>
102 /// Gets or sets the engine context.
103 /// </summary>
104 /// <value>The engine context.</value>
105 public IEngineContext EngineContext
107 get { return engineContext; }
108 set { engineContext = value; }
111 /// <summary>
112 /// Gets the extensions.
113 /// </summary>
114 /// <value>The extensions.</value>
115 public IDictionary<string, object> Extensions
117 get { return extensions; }
120 /// <summary>
121 /// Calls the specified function with the optional arguments.
122 /// </summary>
123 /// <param name="function">The function name.</param>
124 /// <param name="args">The arguments.</param>
125 /// <example>
126 /// The following example uses nvelocity syntax:
127 /// <code>
128 /// $page.call('myJsFunctionAlreadyDeclared', '10', "'message'", $somethingfrompropertybag, $anothermessage.to_squote)
129 /// </code>
130 /// <para>
131 /// Which outputs:
132 /// </para>
133 /// <code>
134 /// myJsFunctionAlreadyDeclared(10, 'message', 1001, 'who let the dogs out?')
135 /// </code>
136 /// </example>
137 public void Call(object function, params object[] args)
139 if (String.IsNullOrEmpty(function.ToString()))
141 throw new ArgumentException("function cannot be null or an empty string.", "function");
144 Record(function + "(" + BuildJSArguments(args) + ")");
147 /// <summary>
148 /// Writes the content specified to the generator instance
149 /// </summary>
150 /// <param name="content">The content.</param>
151 /// <remarks>
152 /// This is for advanced scenarios and for the infrastructure. Usually not useful.
153 /// </remarks>
154 public void Write(string content)
156 lines.Append(content);
159 /// <summary>
160 /// Outputs the content using the renderOptions approach.
161 /// <para>
162 /// If the renderOptions is a string, the content is escaped and quoted.
163 /// </para>
164 /// <para>
165 /// If the renderOptions is a dictionary, we extract the key <c>partial</c>
166 /// and evaluate the template it points to. The content is escaped and quoted.
167 /// </para>
168 /// </summary>
169 /// <param name="renderOptions">The render options.</param>
170 /// <returns></returns>
171 /// <example>
172 /// The following example uses nvelocity syntax:
173 /// <code>
174 /// $page.Call('myJsFunction', $page.render("%{partial='shared/newmessage.vm'}") )
175 /// </code>
176 /// <para>
177 /// Which outputs:
178 /// </para>
179 /// <code>
180 /// myJsFunction('the content from the newmessage partial view template')
181 /// </code>
182 /// </example>
183 public object Render(object renderOptions)
185 if (renderOptions == null)
187 throw new ArgumentNullException("renderOptions",
188 "renderOptions cannot be null. Must be a string or a dictionary");
190 else if (renderOptions is IDictionary)
192 IDictionary options = (IDictionary) renderOptions;
194 String partialName = (String) options["partial"];
196 if (partialName == null)
198 throw new ArgumentNullException("renderOptions",
199 "renderOptions, as a dictionary, must have a 'partial' " +
200 "entry with the template name to render");
205 StringWriter writer = new StringWriter();
207 viewEngineManager.ProcessPartial(partialName, writer, engineContext, controller, context);
209 // Ideally we would call (less overhead and safer)
210 // viewEngineManager.ProcessPartial(partialName, writer, engineContext, parameters);
212 renderOptions = writer.ToString();
214 catch(Exception ex)
216 throw new MonoRailException("Could not process partial " + partialName, ex);
220 return AbstractHelper.Quote(JsEscape(renderOptions.ToString()));
223 /// <summary>
224 /// Writes the content specified to the generator instance
225 /// </summary>
226 /// <param name="content">The content.</param>
227 /// <remarks>
228 /// This is for advanced scenarios and for the infrastructure. Usually not useful.
229 /// </remarks>
230 public void AppendLine(string content)
232 Record(content);
235 /// <summary>
236 /// Gets the js lines.
237 /// </summary>
238 /// <value>The js lines.</value>
239 public StringBuilder Lines
241 get { return lines; }
244 /// <summary>
245 /// Records the specified line on the generator.
246 /// </summary>
247 /// <param name="line">The line.</param>
248 public void Record(string line)
250 Lines.AppendFormat("{0};\r\n", line);
253 /// <summary>
254 /// Builds the JS arguments.
255 /// </summary>
256 /// <param name="args">The args.</param>
257 /// <returns></returns>
258 public string BuildJSArguments(object[] args)
260 if (args == null || args.Length == 0) return String.Empty;
262 StringBuilder tempBuffer = new StringBuilder();
264 bool comma = false;
266 foreach(object arg in args)
268 if (comma) tempBuffer.Append(',');
270 tempBuffer.Append(arg);
272 if (!comma) comma = true;
275 return tempBuffer.ToString();
278 /// <summary>
279 /// Replaces the tail by period.
280 /// </summary>
281 public void ReplaceTailByPeriod()
283 int len = Lines.Length;
285 if (len > 3)
287 RemoveTail();
288 Lines.Append('.');
292 /// <summary>
293 /// Removes the tail.
294 /// </summary>
295 public void RemoveTail()
297 int len = Lines.Length;
299 if (len > 3)
301 if (Lines[len - 3] == ';')
303 Lines.Length = len - 3;
308 /// <summary>
309 /// Generates the final js code.
310 /// </summary>
311 /// <returns></returns>
312 public string GenerateFinalJsCode()
314 return "try \n" +
315 "{\n" + lines +
316 "}\n" +
317 "catch(e)\n" +
318 "{\n" +
319 "alert('JS error ' + e.toString());\n" +
320 "}";
323 /// <summary>
324 /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
325 /// </summary>
326 /// <returns>
327 /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
328 /// </returns>
329 public override string ToString()
331 return GenerateFinalJsCode();
334 #region Internal and private methods
336 /// <summary>
337 /// Escapes the content with C style escapes
338 /// </summary>
339 /// <param name="content">The content.</param>
340 /// <returns></returns>
341 protected static string JsEscape(string content)
343 // This means: replace all variations of line breaks by a \n
344 content = Regex.Replace(content, "(\r\n)|(\r)|(\n)", "\\n", RegexOptions.Multiline);
345 // This is a lookbehind. It means: replace all " -- that are not preceded by \ -- by \"
346 content = Regex.Replace(content, "(?<!\\\\)\"", "\\\"", RegexOptions.Multiline);
347 return content;
350 /// <summary>
351 /// Escapes the content with C style escapes (favoring single quotes)
352 /// </summary>
353 /// <param name="content">The content.</param>
354 /// <returns></returns>
355 protected static string JsEscapeWithSQuotes(string content)
357 // This replaces all ' references by \'
358 return Regex.Replace(JsEscape(content), "(\')", "\\'", RegexOptions.Multiline);
361 #endregion