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;
36 using System.Web.Configuration;
38 using System.Web.UI.HtmlControls;
39 using System.Web.Util;
41 namespace System.Web.Compilation
45 public ControlBuilder Builder;
46 public ILocation Location;
48 public BuilderLocation (ControlBuilder builder, ILocation location)
50 this.Builder = builder;
51 this.Location = location;
55 class BuilderLocationStack : Stack
57 public override void Push (object o)
59 if (!(o is BuilderLocation))
60 throw new InvalidOperationException ();
65 public virtual void Push (ControlBuilder builder, ILocation location)
67 BuilderLocation bl = new BuilderLocation (builder, location);
71 public new BuilderLocation Peek ()
73 return (BuilderLocation) base.Peek ();
76 public new BuilderLocation Pop ()
78 return (BuilderLocation) base.Pop ();
81 public ControlBuilder Builder {
82 get { return Peek ().Builder; }
94 files = new Hashtable (); // may be this should be case sensitive for windows
95 parsers = new Stack ();
98 public bool Push (AspParser parser)
100 if (files.Contains (parser.Filename))
103 files [parser.Filename] = true;
104 parsers.Push (parser);
109 public AspParser Pop ()
111 if (parsers.Count == 0)
114 files.Remove (current.Filename);
115 AspParser result = (AspParser) parsers.Pop ();
116 if (parsers.Count > 0)
117 current = (AspParser) parsers.Peek ();
124 public AspParser Parser {
125 get { return current; }
128 public string Filename {
129 get { return current.Filename; }
142 public void Push (string tagid)
152 return (string) tags.Pop ();
155 public bool CompareTo (string tagid)
160 return 0 == String.Compare (tagid, (string) tags.Peek (), true);
164 get { return tags.Count; }
167 public string Current {
168 get { return (string) tags.Peek (); }
175 BuilderLocationStack stack;
176 TemplateParser tparser;
178 RootBuilder rootBuilder;
179 bool inScript, javascript;
182 StringBuilder tagInnerText = new StringBuilder ();
183 static Hashtable emptyHash = new Hashtable ();
187 public AspGenerator (TemplateParser tparser)
189 this.tparser = tparser;
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 if (type == typeof(MasterPageParser))
219 return new UserControlCompiler ((UserControlParser) tparser);
222 throw new Exception ("Got type: " + type);
225 void InitParser (string filename)
227 StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
228 AspParser parser = new AspParser (filename, reader);
230 parser.Error += new ParseErrorHandler (ParseError);
231 parser.TagParsed += new TagParsedHandler (TagParsed);
232 parser.TextParsed += new TextParsedHandler (TextParsed);
233 if (!pstack.Push (parser))
234 throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
235 tparser.AddDependency (filename);
240 pstack.Parser.Parse ();
247 public Type GetCompiledType ()
249 Type type = (Type) HttpRuntime.Cache.Get ("@@Type" + tparser.InputFile);
254 isApplication = tparser.DefaultDirectiveName == "application";
255 InitParser (Path.GetFullPath (tparser.InputFile));
259 PrintTree (rootBuilder, 0);
263 throw new ParseException (stack.Builder.location,
264 "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
266 BaseCompiler compiler = GetCompilerFromType ();
268 type = compiler.GetCompiledType ();
269 CacheDependency cd = new CacheDependency ((string[])
270 tparser.Dependencies.ToArray (typeof (string)));
272 HttpRuntime.Cache.InsertPrivate ("@@Type" + tparser.InputFile, type, cd);
277 static void PrintTree (ControlBuilder builder, int indent)
282 string i = new string ('\t', indent);
284 Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
285 builder, builder.ID, builder.ControlType, builder.parentBuilder);
287 if (builder.Children != null)
288 foreach (object o in builder.Children) {
289 if (o is ControlBuilder)
290 PrintTree ((ControlBuilder) o, indent++);
294 static void PrintLocation (ILocation loc)
296 Console.WriteLine ("\tFile name: " + loc.Filename);
297 Console.WriteLine ("\tBegin line: " + loc.BeginLine);
298 Console.WriteLine ("\tEnd line: " + loc.EndLine);
299 Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
300 Console.WriteLine ("\tEnd column: " + loc.EndColumn);
301 Console.WriteLine ("\tPlainText: " + loc.PlainText);
302 Console.WriteLine ();
306 void ParseError (ILocation location, string message)
308 throw new ParseException (location, message);
311 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
313 this.location = new Location (location);
315 tparser.Location = location;
317 if (text.Length != 0)
320 if (0 == String.Compare (tagid, "script", true)) {
321 if (inScript || (tagtype != TagType.Close && attributes != null)) {
322 if ((inScript || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes))
328 case TagType.Directive:
330 tagid = tparser.DefaultDirectiveName;
332 tparser.AddDirective (tagid, attributes.GetDictionary (null));
335 if (ProcessTag (tagid, attributes, tagtype)) {
341 stack.Builder.EnsureOtherTags ();
342 stack.Builder.OtherTags.Add (tagid);
345 TextParsed (location, location.PlainText);
348 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
349 if (!notServer && CloseControl (tagid))
352 TextParsed (location, location.PlainText);
354 case TagType.SelfClosing:
355 int count = stack.Count;
356 if (!ProcessTag (tagid, attributes, tagtype)) {
357 TextParsed (location, location.PlainText);
358 } else if (stack.Count != count) {
359 CloseControl (tagid);
362 case TagType.DataBinding:
363 goto case TagType.CodeRender;
364 case TagType.CodeRenderExpression:
365 goto case TagType.CodeRender;
366 case TagType.CodeRender:
368 throw new ParseException (location, "Invalid content for application file.");
370 ProcessCode (tagtype, tagid, location);
372 case TagType.Include:
374 throw new ParseException (location, "Invalid content for application file.");
376 string file = attributes ["virtual"] as string;
377 bool isvirtual = (file != null);
379 file = attributes ["file"] as string;
382 file = tparser.MapPath (file);
384 file = GetIncludeFilePath (tparser.BaseDir, file);
393 //PrintLocation (location);
396 static bool TryRemoveTag (string tagid, ArrayList otags)
398 if (otags == null || otags.Count == 0)
401 int idx = otags.Count - 1;
402 string otagid = (string) otags [idx];
403 if (0 != String.Compare (tagid, otagid, true))
406 otags.RemoveAt (idx);
410 static string GetIncludeFilePath (string basedir, string filename)
412 if (Path.DirectorySeparatorChar == '/')
413 filename = filename.Replace ("\\", "/");
415 return Path.GetFullPath (Path.Combine (basedir, filename));
418 void TextParsed (ILocation location, string text)
420 if (text.IndexOf ("<%") != -1 && !inScript) {
421 if (this.text.Length > 0)
423 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
428 this.text.Append (text);
429 //PrintLocation (location);
434 string t = text.ToString ();
437 // TODO: store location
438 tparser.Scripts.Add (t);
442 if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
443 throw new ParseException (location, "Content not valid for application file.");
445 ControlBuilder current = stack.Builder;
446 current.AppendLiteralString (t);
447 if (current.NeedsTagInnerText ()) {
448 tagInnerText.Append (t);
452 bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
454 if ((atts == null || !atts.IsRunAtServer ()) && String.Compare (tagid, "tbody", true) == 0) {
455 // MS completely ignores tbody or, if runat="server", fails when compiling
457 return stack.Builder.ChildrenAsProperties;
463 if (String.Compare (tagid, "object", true) != 0)
464 throw new ParseException (location, "Invalid tag for application file.");
467 ControlBuilder parent = stack.Builder;
468 ControlBuilder builder = null;
469 Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
470 if (stack.Count > 1) {
472 builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
473 } catch (TypeLoadException e) {
474 throw new ParseException (Location, "Type not found.", e);
475 } catch (Exception e) {
476 throw new ParseException (Location, e.Message, e);
480 if (builder == null && atts != null && atts.IsRunAtServer ()) {
481 string id = htable ["id"] as string;
482 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
483 throw new ParseException (Location, "'" + id + "' is not a valid identifier");
486 builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
487 } catch (TypeLoadException e) {
488 throw new ParseException (Location, "Type not found.", e);
489 } catch (Exception e) {
490 throw new ParseException (Location, e.Message, e);
497 builder.location = location;
498 builder.ID = htable ["id"] as string;
499 if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
501 throw new ParseException (location, "Only one <form> allowed.");
506 if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
507 if (builder is TemplateBuilder) {
510 stack.Push (builder, location);
512 if (!isApplication && builder is ObjectTagBuilder) {
513 ObjectTagBuilder ot = (ObjectTagBuilder) builder;
514 if (ot.Scope != null && ot.Scope != "")
515 throw new ParseException (location, "Scope not allowed here");
517 if (tagtype == TagType.Tag) {
518 stack.Push (builder, location);
523 parent.AppendSubBuilder (builder);
524 builder.CloseControl ();
530 bool ProcessScript (TagType tagtype, TagAttributes attributes)
532 if (tagtype != TagType.Close) {
533 if (attributes != null && attributes.IsRunAtServer ()) {
534 CheckLanguage ((string) attributes ["language"]);
535 if (tagtype == TagType.Tag) {
536 Parser.VerbatimID = "script";
538 } //else if (tagtype == TagType.SelfClosing)
539 // load script file here
543 if (tagtype != TagType.SelfClosing) {
544 Parser.VerbatimID = "script";
547 TextParsed (location, location.PlainText);
559 TextParsed (location, location.PlainText);
565 bool CloseControl (string tagid)
567 ControlBuilder current = stack.Builder;
568 string btag = current.TagName;
569 if (String.Compare (btag, "tbody", true) != 0 && String.Compare (tagid, "tbody", true) == 0) {
570 if (!current.ChildrenAsProperties) {
572 TextParsed (location, location.PlainText);
579 if (0 != String.Compare (tagid, btag, true))
582 // if (current is TemplateBuilder)
583 // pop from the id list
584 if (current.NeedsTagInnerText ()) {
586 current.SetTagInnerText (tagInnerText.ToString ());
587 } catch (Exception e) {
588 throw new ParseException (current.location, e.Message, e);
591 tagInnerText.Length = 0;
594 if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
598 current.CloseControl ();
600 stack.Builder.AppendSubBuilder (current);
604 bool ProcessCode (TagType tagtype, string code, ILocation location)
606 ControlBuilder b = null;
607 if (tagtype == TagType.CodeRender)
608 b = new CodeRenderBuilder (code, false, location);
609 else if (tagtype == TagType.CodeRenderExpression)
610 b = new CodeRenderBuilder (code, true, location);
611 else if (tagtype == TagType.DataBinding)
612 b = new DataBindingBuilder (code, location);
614 throw new HttpException ("Should never happen");
616 stack.Builder.AppendSubBuilder (b);
620 public ILocation Location {
621 get { return location; }
624 void CheckLanguage (string lang)
626 if (lang == null || lang == "")
629 if (String.Compare (lang, tparser.Language, true) == 0)
632 #if CONFIGURATION_2_0
633 CompilationSection section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
634 if (section.Compilers[tparser.Language] != section.Compilers[lang])
636 CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current);
637 if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
639 throw new ParseException (Location,
640 String.Format ("Trying to mix language '{0}' and '{1}'.",
641 tparser.Language, lang));
644 // Used to get CodeRender tags in attribute values
645 class CodeRenderParser
648 ControlBuilder builder;
650 public CodeRenderParser (string str, ControlBuilder builder)
653 this.builder = builder;
656 public void AddChildren ()
658 int index = str.IndexOf ("<%");
660 TextParsed (null, str.Substring (0, index));
661 str = str.Substring (index);
664 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
665 parser.Error += new ParseErrorHandler (ParseError);
666 parser.TagParsed += new TagParsedHandler (TagParsed);
667 parser.TextParsed += new TextParsedHandler (TextParsed);
671 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
673 if (tagtype == TagType.CodeRender)
674 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
675 else if (tagtype == TagType.CodeRenderExpression)
676 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
677 else if (tagtype == TagType.DataBinding)
678 builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
680 builder.AppendLiteralString (location.PlainText);
683 void TextParsed (ILocation location, string text)
685 builder.AppendLiteralString (text);
688 void ParseError (ILocation location, string message)
690 throw new ParseException (location, message);