2 // System.Web.Compilation.AspGenerator
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
8 // Copyright (c) 2004,2006 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections;
33 using System.CodeDom.Compiler;
34 using System.Globalization;
37 using System.Web.Caching;
38 using System.Web.Configuration;
40 using System.Web.UI.HtmlControls;
41 using System.Web.Util;
43 namespace System.Web.Compilation
47 public ControlBuilder Builder;
48 public ILocation Location;
50 public BuilderLocation (ControlBuilder builder, ILocation location)
52 this.Builder = builder;
53 this.Location = location;
57 class BuilderLocationStack : Stack
59 public override void Push (object o)
61 if (!(o is BuilderLocation))
62 throw new InvalidOperationException ();
67 public virtual void Push (ControlBuilder builder, ILocation location)
69 BuilderLocation bl = new BuilderLocation (builder, location);
73 public new BuilderLocation Peek ()
75 return (BuilderLocation) base.Peek ();
78 public new BuilderLocation Pop ()
80 return (BuilderLocation) base.Pop ();
83 public ControlBuilder Builder {
84 get { return Peek ().Builder; }
96 files = new Hashtable (); // may be this should be case sensitive for windows
97 parsers = new Stack ();
100 public bool Push (AspParser parser)
102 if (files.Contains (parser.Filename))
105 files [parser.Filename] = true;
106 parsers.Push (parser);
111 public AspParser Pop ()
113 if (parsers.Count == 0)
116 files.Remove (current.Filename);
117 AspParser result = (AspParser) parsers.Pop ();
118 if (parsers.Count > 0)
119 current = (AspParser) parsers.Peek ();
127 get { return parsers.Count; }
130 public AspParser Parser {
131 get { return current; }
134 public string Filename {
135 get { return current.Filename; }
148 public void Push (string tagid)
158 return (string) tags.Pop ();
161 public bool CompareTo (string tagid)
166 return 0 == String.Compare (tagid, (string) tags.Peek (), true, CultureInfo.InvariantCulture);
170 get { return tags.Count; }
173 public string Current {
174 get { return (string) tags.Peek (); }
181 BuilderLocationStack stack;
182 TemplateParser tparser;
184 RootBuilder rootBuilder;
185 bool inScript, javascript, ignore_text;
188 StringBuilder tagInnerText = new StringBuilder ();
189 static Hashtable emptyHash = new Hashtable ();
193 public AspGenerator (TemplateParser tparser)
195 this.tparser = tparser;
196 text = new StringBuilder ();
197 stack = new BuilderLocationStack ();
198 rootBuilder = new RootBuilder (tparser);
199 stack.Push (rootBuilder, null);
200 tparser.RootBuilder = rootBuilder;
201 pstack = new ParserStack ();
204 public RootBuilder RootBuilder {
205 get { return tparser.RootBuilder; }
208 public AspParser Parser {
209 get { return pstack.Parser; }
212 public string Filename {
213 get { return pstack.Filename; }
216 BaseCompiler GetCompilerFromType ()
218 Type type = tparser.GetType ();
219 if (type == typeof (PageParser))
220 return new PageCompiler ((PageParser) tparser);
222 if (type == typeof (ApplicationFileParser))
223 return new GlobalAsaxCompiler ((ApplicationFileParser) tparser);
225 if (type == typeof (UserControlParser))
226 return new UserControlCompiler ((UserControlParser) tparser);
228 if (type == typeof(MasterPageParser))
229 return new MasterPageCompiler ((MasterPageParser) tparser);
232 throw new Exception ("Got type: " + type);
235 void InitParser (string filename)
237 StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
238 AspParser parser = new AspParser (filename, reader);
240 parser.Error += new ParseErrorHandler (ParseError);
241 parser.TagParsed += new TagParsedHandler (TagParsed);
242 parser.TextParsed += new TextParsedHandler (TextParsed);
243 if (!pstack.Push (parser))
244 throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
245 tparser.AddDependency (filename);
248 public void Parse (string file)
252 pstack.Parser.Parse ();
259 PrintTree (rootBuilder, 0);
262 if (stack.Count > 1 && pstack.Count == 0)
263 throw new ParseException (stack.Builder.location,
264 "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
269 Parse (Path.GetFullPath (tparser.InputFile));
272 internal static void AddTypeToCache (ArrayList dependencies, string inputFile, Type type)
274 CacheDependency cd = new CacheDependency ((string[])dependencies.ToArray (typeof (string)));
275 HttpRuntime.Cache.InsertPrivate ("@@Type" + inputFile, type, cd);
278 public Type GetCompiledType ()
280 Type type = (Type) HttpRuntime.Cache.Get ("@@Type" + tparser.InputFile);
285 isApplication = tparser.DefaultDirectiveName == "application";
288 BaseCompiler compiler = GetCompilerFromType ();
290 type = compiler.GetCompiledType ();
291 AddTypeToCache (tparser.Dependencies, tparser.InputFile, type);
296 static void PrintTree (ControlBuilder builder, int indent)
301 string i = new string ('\t', indent);
303 Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
304 builder, builder.ID, builder.ControlType, builder.parentBuilder);
306 if (builder.Children != null)
307 foreach (object o in builder.Children) {
308 if (o is ControlBuilder)
309 PrintTree ((ControlBuilder) o, indent++);
313 static void PrintLocation (ILocation loc)
315 Console.WriteLine ("\tFile name: " + loc.Filename);
316 Console.WriteLine ("\tBegin line: " + loc.BeginLine);
317 Console.WriteLine ("\tEnd line: " + loc.EndLine);
318 Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
319 Console.WriteLine ("\tEnd column: " + loc.EndColumn);
320 Console.WriteLine ("\tPlainText: " + loc.PlainText);
321 Console.WriteLine ();
325 void ParseError (ILocation location, string message)
327 throw new ParseException (location, message);
330 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
332 this.location = new Location (location);
334 tparser.Location = location;
336 if (text.Length != 0)
339 if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) {
340 bool in_script = (inScript || ignore_text);
341 if (in_script || (tagtype != TagType.Close && attributes != null)) {
342 if ((in_script || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes))
348 case TagType.Directive:
350 tagid = tparser.DefaultDirectiveName;
352 tparser.AddDirective (tagid, attributes.GetDictionary (null));
355 if (ProcessTag (tagid, attributes, tagtype)) {
361 stack.Builder.EnsureOtherTags ();
362 stack.Builder.OtherTags.Add (tagid);
365 TextParsed (location, location.PlainText);
368 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
369 if (!notServer && CloseControl (tagid))
372 TextParsed (location, location.PlainText);
374 case TagType.SelfClosing:
375 int count = stack.Count;
376 if (!ProcessTag (tagid, attributes, tagtype)) {
377 TextParsed (location, location.PlainText);
378 } else if (stack.Count != count) {
379 CloseControl (tagid);
382 case TagType.DataBinding:
383 goto case TagType.CodeRender;
384 case TagType.CodeRenderExpression:
385 goto case TagType.CodeRender;
386 case TagType.CodeRender:
388 throw new ParseException (location, "Invalid content for application file.");
390 ProcessCode (tagtype, tagid, location);
392 case TagType.Include:
394 throw new ParseException (location, "Invalid content for application file.");
396 string file = attributes ["virtual"] as string;
397 bool isvirtual = (file != null);
399 file = attributes ["file"] as string;
402 file = tparser.MapPath (file);
404 file = GetIncludeFilePath (tparser.BaseDir, file);
412 //PrintLocation (location);
415 static bool TryRemoveTag (string tagid, ArrayList otags)
417 if (otags == null || otags.Count == 0)
420 for (int idx = otags.Count - 1; idx >= 0; idx--) {
421 string otagid = (string) otags [idx];
422 if (0 == String.Compare (tagid, otagid, true, CultureInfo.InvariantCulture)) {
424 otags.RemoveAt (idx);
425 } while (otags.Count - 1 >= idx);
432 static string GetIncludeFilePath (string basedir, string filename)
434 if (Path.DirectorySeparatorChar == '/')
435 filename = filename.Replace ("\\", "/");
437 return Path.GetFullPath (Path.Combine (basedir, filename));
440 void TextParsed (ILocation location, string text)
445 if (text.IndexOf ("<%") != -1 && !inScript) {
446 if (this.text.Length > 0)
448 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
453 this.text.Append (text);
454 //PrintLocation (location);
459 string t = text.ToString ();
462 // TODO: store location
463 tparser.Scripts.Add (t);
467 if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
468 throw new ParseException (location, "Content not valid for application file.");
470 ControlBuilder current = stack.Builder;
471 current.AppendLiteralString (t);
472 if (current.NeedsTagInnerText ()) {
473 tagInnerText.Append (t);
477 bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
480 if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0)
481 throw new ParseException (location, "Invalid tag for application file.");
484 ControlBuilder parent = stack.Builder;
485 ControlBuilder builder = null;
486 Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
487 if (stack.Count > 1) {
489 builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
490 } catch (TypeLoadException e) {
491 throw new ParseException (Location, "Type not found.", e);
492 } catch (Exception e) {
493 throw new ParseException (Location, e.Message, e);
497 if (builder == null && atts != null && atts.IsRunAtServer ()) {
498 string id = htable ["id"] as string;
499 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
500 throw new ParseException (Location, "'" + id + "' is not a valid identifier");
503 builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
504 } catch (TypeLoadException e) {
505 throw new ParseException (Location, "Type not found.", e);
506 } catch (Exception e) {
507 throw new ParseException (Location, e.Message, e);
514 builder.location = location;
515 builder.ID = htable ["id"] as string;
516 if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
518 throw new ParseException (location, "Only one <form> allowed.");
523 if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
524 if (builder is TemplateBuilder) {
527 stack.Push (builder, location);
529 if (!isApplication && builder is ObjectTagBuilder) {
530 ObjectTagBuilder ot = (ObjectTagBuilder) builder;
531 if (ot.Scope != null && ot.Scope != "")
532 throw new ParseException (location, "Scope not allowed here");
534 if (tagtype == TagType.Tag) {
535 stack.Push (builder, location);
540 parent.AppendSubBuilder (builder);
541 builder.CloseControl ();
547 string ReadFile (string filename)
549 string realpath = tparser.MapPath (filename);
550 using (StreamReader sr = new StreamReader (realpath, WebEncoding.FileEncoding)) {
551 string content = sr.ReadToEnd ();
556 bool ProcessScript (TagType tagtype, TagAttributes attributes)
558 if (tagtype != TagType.Close) {
559 if (attributes != null && attributes.IsRunAtServer ()) {
560 CheckLanguage ((string) attributes ["language"]);
561 string src = (string) attributes ["src"];
564 throw new ParseException (Parser,
565 "src cannot be an empty string");
567 string content = ReadFile (src);
569 TextParsed (Parser, content);
572 if (tagtype != TagType.SelfClosing) {
574 Parser.VerbatimID = "script";
576 } else if (tagtype == TagType.Tag) {
577 Parser.VerbatimID = "script";
583 if (tagtype != TagType.SelfClosing) {
584 Parser.VerbatimID = "script";
587 TextParsed (location, location.PlainText);
596 } else if (!ignore_text) {
599 TextParsed (location, location.PlainText);
608 bool CloseControl (string tagid)
610 ControlBuilder current = stack.Builder;
611 string btag = current.TagName;
612 if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
613 String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
614 if (!current.ChildrenAsProperties) {
616 TextParsed (location, location.PlainText);
623 if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture))
626 // if (current is TemplateBuilder)
627 // pop from the id list
628 if (current.NeedsTagInnerText ()) {
630 current.SetTagInnerText (tagInnerText.ToString ());
631 } catch (Exception e) {
632 throw new ParseException (current.location, e.Message, e);
635 tagInnerText.Length = 0;
638 if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
642 current.CloseControl ();
644 stack.Builder.AppendSubBuilder (current);
648 bool ProcessCode (TagType tagtype, string code, ILocation location)
650 ControlBuilder b = null;
651 if (tagtype == TagType.CodeRender)
652 b = new CodeRenderBuilder (code, false, location);
653 else if (tagtype == TagType.CodeRenderExpression)
654 b = new CodeRenderBuilder (code, true, location);
655 else if (tagtype == TagType.DataBinding)
656 b = new DataBindingBuilder (code, location);
658 throw new HttpException ("Should never happen");
660 stack.Builder.AppendSubBuilder (b);
664 public ILocation Location {
665 get { return location; }
668 void CheckLanguage (string lang)
670 if (lang == null || lang == "")
673 if (String.Compare (lang, tparser.Language, true, CultureInfo.InvariantCulture) == 0)
677 CompilationSection section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
678 if (section.Compilers[tparser.Language] != section.Compilers[lang])
680 CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current);
681 if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
683 throw new ParseException (Location,
684 String.Format ("Trying to mix language '{0}' and '{1}'.",
685 tparser.Language, lang));
688 // Used to get CodeRender tags in attribute values
689 class CodeRenderParser
692 ControlBuilder builder;
694 public CodeRenderParser (string str, ControlBuilder builder)
697 this.builder = builder;
700 public void AddChildren ()
702 int index = str.IndexOf ("<%");
704 TextParsed (null, str.Substring (0, index));
705 str = str.Substring (index);
708 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
709 parser.Error += new ParseErrorHandler (ParseError);
710 parser.TagParsed += new TagParsedHandler (TagParsed);
711 parser.TextParsed += new TextParsedHandler (TextParsed);
715 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
717 if (tagtype == TagType.CodeRender)
718 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
719 else if (tagtype == TagType.CodeRenderExpression)
720 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
721 else if (tagtype == TagType.DataBinding)
722 builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
724 builder.AppendLiteralString (location.PlainText);
727 void TextParsed (ILocation location, string text)
729 builder.AppendLiteralString (text);
732 void ParseError (ILocation location, string message)
734 throw new ParseException (location, message);