2 // System.Web.Compilation.AspGenerator
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
32 using System.CodeDom.Compiler;
35 using System.Web.Caching;
37 using System.Web.UI.HtmlControls;
38 using System.Web.Util;
40 namespace System.Web.Compilation
44 public ControlBuilder Builder;
45 public ILocation Location;
47 public BuilderLocation (ControlBuilder builder, ILocation location)
49 this.Builder = builder;
50 this.Location = location;
54 class BuilderLocationStack : Stack
56 public override void Push (object o)
58 if (!(o is BuilderLocation))
59 throw new InvalidOperationException ();
64 public virtual void Push (ControlBuilder builder, ILocation location)
66 BuilderLocation bl = new BuilderLocation (builder, location);
70 public new BuilderLocation Peek ()
72 return (BuilderLocation) base.Peek ();
75 public new BuilderLocation Pop ()
77 return (BuilderLocation) base.Pop ();
80 public ControlBuilder Builder {
81 get { return Peek ().Builder; }
93 files = new Hashtable (); // may be this should be case sensitive for windows
94 parsers = new Stack ();
97 public bool Push (AspParser parser)
99 if (files.Contains (parser.Filename))
102 files [parser.Filename] = true;
103 parsers.Push (parser);
108 public AspParser Pop ()
110 if (parsers.Count == 0)
113 files.Remove (current.Filename);
114 AspParser result = (AspParser) parsers.Pop ();
115 if (parsers.Count > 0)
116 current = (AspParser) parsers.Peek ();
123 public AspParser Parser {
124 get { return current; }
127 public string Filename {
128 get { return current.Filename; }
141 public void Push (string tagid)
151 return (string) tags.Pop ();
154 public bool CompareTo (string tagid)
159 return 0 == String.Compare (tagid, (string) tags.Peek (), true);
163 get { return tags.Count; }
166 public string Current {
167 get { return (string) tags.Peek (); }
174 BuilderLocationStack stack;
175 TemplateParser tparser;
177 RootBuilder rootBuilder;
178 bool inScript, javascript;
181 StringBuilder tagInnerText = new StringBuilder ();
182 static Hashtable emptyHash = new Hashtable ();
186 public AspGenerator (TemplateParser tparser)
188 this.tparser = tparser;
189 tparser.AddDependency (tparser.InputFile);
190 text = new StringBuilder ();
191 stack = new BuilderLocationStack ();
192 rootBuilder = new RootBuilder (tparser);
193 stack.Push (rootBuilder, null);
194 tparser.RootBuilder = rootBuilder;
195 pstack = new ParserStack ();
198 public AspParser Parser {
199 get { return pstack.Parser; }
202 public string Filename {
203 get { return pstack.Filename; }
206 BaseCompiler GetCompilerFromType ()
208 Type type = tparser.GetType ();
209 if (type == typeof (PageParser))
210 return new PageCompiler ((PageParser) tparser);
212 if (type == typeof (ApplicationFileParser))
213 return new GlobalAsaxCompiler ((ApplicationFileParser) tparser);
215 if (type == typeof (UserControlParser))
216 return new UserControlCompiler ((UserControlParser) tparser);
218 throw new Exception ("Got type: " + type);
221 void InitParser (string filename)
223 StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
224 AspParser parser = new AspParser (filename, reader);
226 parser.Error += new ParseErrorHandler (ParseError);
227 parser.TagParsed += new TagParsedHandler (TagParsed);
228 parser.TextParsed += new TextParsedHandler (TextParsed);
229 if (!pstack.Push (parser))
230 throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
231 tparser.AddDependency (filename);
236 pstack.Parser.Parse ();
243 public Type GetCompiledType ()
245 Type type = (Type) HttpRuntime.Cache.Get ("@@Type" + tparser.InputFile);
250 isApplication = tparser.DefaultDirectiveName == "application";
251 InitParser (Path.GetFullPath (tparser.InputFile));
255 PrintTree (rootBuilder, 0);
259 throw new ParseException (stack.Builder.location,
260 "Expecting </" + stack.Builder.TagName + ">" + stack.Builder);
262 BaseCompiler compiler = GetCompilerFromType ();
264 type = compiler.GetCompiledType ();
265 CacheDependency cd = new CacheDependency ((string[])
266 tparser.Dependencies.ToArray (typeof (string)));
268 HttpRuntime.Cache.Insert ("@@Type" + tparser.InputFile, type, cd);
273 static void PrintTree (ControlBuilder builder, int indent)
278 string i = new string ('\t', indent);
280 Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
281 builder, builder.ID, builder.ControlType, builder.parentBuilder);
283 if (builder.Children != null)
284 foreach (object o in builder.Children) {
285 if (o is ControlBuilder)
286 PrintTree ((ControlBuilder) o, indent++);
291 static void PrintLocation (ILocation loc)
293 Console.WriteLine ("\tFile name: " + loc.Filename);
294 Console.WriteLine ("\tBegin line: " + loc.BeginLine);
295 Console.WriteLine ("\tEnd line: " + loc.EndLine);
296 Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
297 Console.WriteLine ("\tEnd column: " + loc.EndColumn);
298 Console.WriteLine ("\tPlainText: " + loc.PlainText);
299 Console.WriteLine ();
302 void ParseError (ILocation location, string message)
304 throw new ParseException (location, message);
307 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
309 this.location = new Location (location);
311 tparser.Location = location;
313 if (text.Length != 0)
316 if (0 == String.Compare (tagid, "script", true)) {
317 if (ProcessScript (tagtype, attributes))
322 case TagType.Directive:
324 tagid = tparser.DefaultDirectiveName;
326 tparser.AddDirective (tagid, attributes.GetDictionary (null));
329 if (ProcessTag (tagid, attributes, tagtype))
333 stack.Builder.EnsureOtherTags ();
334 stack.Builder.OtherTags.Add (tagid);
337 TextParsed (location, location.PlainText);
340 bool notServer = (inForm && TryRemoveTag (tagid, stack.Builder.OtherTags));
341 if (!notServer && CloseControl (tagid))
344 TextParsed (location, location.PlainText);
346 case TagType.SelfClosing:
347 int count = stack.Count;
348 if (!ProcessTag (tagid, attributes, tagtype)) {
349 TextParsed (location, location.PlainText);
350 } else if (stack.Count != count) {
351 CloseControl (tagid);
354 case TagType.DataBinding:
355 goto case TagType.CodeRender;
356 case TagType.CodeRenderExpression:
357 goto case TagType.CodeRender;
358 case TagType.CodeRender:
360 throw new ParseException (location, "Invalid content for application file.");
362 ProcessCode (tagtype, tagid, location);
364 case TagType.Include:
366 throw new ParseException (location, "Invalid content for application file.");
368 string file = attributes ["virtual"] as string;
369 bool isvirtual = (file != null);
371 file = attributes ["file"] as string;
374 file = tparser.MapPath (file);
376 file = GetIncludeFilePath (tparser.BaseDir, file);
385 //PrintLocation (location);
388 static bool TryRemoveTag (string tagid, ArrayList otags)
390 if (otags == null || otags.Count == 0)
393 int idx = otags.Count - 1;
394 string otagid = (string) otags [idx];
395 if (0 != String.Compare (tagid, otagid, true))
398 otags.RemoveAt (idx);
402 static string GetIncludeFilePath (string basedir, string filename)
404 if (Path.DirectorySeparatorChar == '/')
405 filename = filename.Replace ("\\", "/");
407 return Path.GetFullPath (Path.Combine (basedir, filename));
410 void TextParsed (ILocation location, string text)
412 if (text.IndexOf ("<%") != -1 && !inScript) {
413 if (this.text.Length > 0)
415 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
420 this.text.Append (text);
421 //PrintLocation (location);
426 string t = text.ToString ();
429 // TODO: store location
430 tparser.Scripts.Add (t);
434 if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
435 throw new ParseException (location, "Content not valid for application file.");
437 ControlBuilder current = stack.Builder;
438 current.AppendLiteralString (t);
439 if (current.NeedsTagInnerText ()) {
440 tagInnerText.Append (t);
444 bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
446 if ((atts == null || !atts.IsRunAtServer ()) && String.Compare (tagid, "tbody", true) == 0) {
447 // MS completely ignores tbody or, if runat="server", fails when compiling
449 return stack.Builder.ChildrenAsProperties;
455 if (String.Compare (tagid, "object", true) != 0)
456 throw new ParseException (location, "Invalid tag for application file.");
459 ControlBuilder parent = stack.Builder;
460 ControlBuilder builder = null;
461 Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
462 if (stack.Count > 1) {
464 builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
465 } catch (TypeLoadException e) {
466 throw new ParseException (Location, "Type not found.", e);
467 } catch (Exception e) {
468 throw new ParseException (Location, e.Message, e);
472 if (builder == null && atts != null && atts.IsRunAtServer ()) {
473 string id = htable ["id"] as string;
474 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
475 throw new ParseException (Location, "'" + id + "' is not a valid identifier");
478 builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
479 } catch (TypeLoadException e) {
480 throw new ParseException (Location, "Type not found.", e);
481 } catch (Exception e) {
482 throw new ParseException (Location, e.Message, e);
489 builder.location = location;
490 builder.ID = htable ["id"] as string;
491 if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
493 throw new ParseException (location, "Only one <form> allowed.");
496 formTags = new TagStack ();
499 if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
500 if (builder is TemplateBuilder) {
503 stack.Push (builder, location);
505 if (!isApplication && builder is ObjectTagBuilder) {
506 ObjectTagBuilder ot = (ObjectTagBuilder) builder;
507 if (ot.Scope != null && ot.Scope != "")
508 throw new ParseException (location, "Scope not allowed here");
510 if (tagtype == TagType.Tag) {
511 stack.Push (builder, location);
516 parent.AppendSubBuilder (builder);
517 builder.CloseControl ();
523 bool ProcessScript (TagType tagtype, TagAttributes attributes)
525 if (tagtype != TagType.Close) {
526 if (attributes != null && attributes.IsRunAtServer ()) {
527 CheckLanguage ((string) attributes ["language"]);
528 if (tagtype == TagType.Tag) {
529 Parser.VerbatimID = "script";
531 } //else if (tagtype == TagType.SelfClosing)
532 // load script file here
536 if (tagtype != TagType.SelfClosing) {
537 Parser.VerbatimID = "script";
540 TextParsed (location, location.PlainText);
552 TextParsed (location, location.PlainText);
558 bool CloseControl (string tagid)
560 ControlBuilder current = stack.Builder;
561 if (String.Compare (tagid, "tbody", true) == 0) {
562 if (!current.ChildrenAsProperties) {
564 TextParsed (location, location.PlainText);
571 string btag = current.TagName;
572 if (0 != String.Compare (tagid, btag, true))
575 // if (current is TemplateBuilder)
576 // pop from the id list
577 if (current.NeedsTagInnerText ()) {
579 current.SetTagInnerText (tagInnerText.ToString ());
580 } catch (Exception e) {
581 throw new ParseException (current.location, e.Message, e);
584 tagInnerText.Length = 0;
587 if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
591 current.CloseControl ();
593 stack.Builder.AppendSubBuilder (current);
597 bool ProcessCode (TagType tagtype, string code, ILocation location)
599 ControlBuilder b = null;
600 if (tagtype == TagType.CodeRender)
601 b = new CodeRenderBuilder (code, false, location);
602 else if (tagtype == TagType.CodeRenderExpression)
603 b = new CodeRenderBuilder (code, true, location);
604 else if (tagtype == TagType.DataBinding)
605 b = new DataBindingBuilder (code, location);
607 throw new HttpException ("Should never happen");
609 stack.Builder.AppendSubBuilder (b);
613 public ILocation Location {
614 get { return location; }
617 void CheckLanguage (string lang)
619 if (lang == null || lang == "")
622 if (String.Compare (lang, tparser.Language, true) != 0) {
623 throw new ParseException (Location,
624 String.Format ("Trying to mix language '{0}' and '{1}'.",
625 tparser.Language, lang));
629 // Used to get CodeRender tags in attribute values
630 class CodeRenderParser
633 ControlBuilder builder;
635 public CodeRenderParser (string str, ControlBuilder builder)
638 this.builder = builder;
641 public void AddChildren ()
643 int index = str.IndexOf ("<%");
645 TextParsed (null, str.Substring (0, index));
646 str = str.Substring (index);
649 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
650 parser.Error += new ParseErrorHandler (ParseError);
651 parser.TagParsed += new TagParsedHandler (TagParsed);
652 parser.TextParsed += new TextParsedHandler (TextParsed);
656 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
658 if (tagtype == TagType.CodeRender)
659 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
660 else if (tagtype == TagType.CodeRenderExpression)
661 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
662 else if (tagtype == TagType.DataBinding)
663 builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
665 builder.AppendLiteralString (location.PlainText);
668 void TextParsed (ILocation location, string text)
670 builder.AppendLiteralString (text);
673 void ParseError (ILocation location, string message)
675 throw new ParseException (location, message);