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 public Type GetCompiledType ()
274 Type type = (Type) HttpRuntime.Cache.Get ("@@Type" + tparser.InputFile);
279 isApplication = tparser.DefaultDirectiveName == "application";
283 BaseCompiler compiler = GetCompilerFromType ();
285 type = compiler.GetCompiledType ();
286 CacheDependency cd = new CacheDependency ((string[])
287 tparser.Dependencies.ToArray (typeof (string)));
289 HttpRuntime.Cache.InsertPrivate ("@@Type" + tparser.InputFile, type, cd);
294 static void PrintTree (ControlBuilder builder, int indent)
299 string i = new string ('\t', indent);
301 Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
302 builder, builder.ID, builder.ControlType, builder.parentBuilder);
304 if (builder.Children != null)
305 foreach (object o in builder.Children) {
306 if (o is ControlBuilder)
307 PrintTree ((ControlBuilder) o, indent++);
311 static void PrintLocation (ILocation loc)
313 Console.WriteLine ("\tFile name: " + loc.Filename);
314 Console.WriteLine ("\tBegin line: " + loc.BeginLine);
315 Console.WriteLine ("\tEnd line: " + loc.EndLine);
316 Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
317 Console.WriteLine ("\tEnd column: " + loc.EndColumn);
318 Console.WriteLine ("\tPlainText: " + loc.PlainText);
319 Console.WriteLine ();
323 void ParseError (ILocation location, string message)
325 throw new ParseException (location, message);
328 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
330 this.location = new Location (location);
332 tparser.Location = location;
334 // MS ignores tbody/thead
335 if ((attributes == null || !attributes.IsRunAtServer ())) {
336 if (String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0 ||
337 String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0)
341 if (text.Length != 0)
344 if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) {
345 bool in_script = (inScript || ignore_text);
346 if (in_script || (tagtype != TagType.Close && attributes != null)) {
347 if ((in_script || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes))
353 case TagType.Directive:
355 tagid = tparser.DefaultDirectiveName;
357 tparser.AddDirective (tagid, attributes.GetDictionary (null));
360 if (ProcessTag (tagid, attributes, tagtype)) {
366 stack.Builder.EnsureOtherTags ();
367 stack.Builder.OtherTags.Add (tagid);
370 TextParsed (location, location.PlainText);
373 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
374 if (!notServer && CloseControl (tagid))
377 TextParsed (location, location.PlainText);
379 case TagType.SelfClosing:
380 int count = stack.Count;
381 if (!ProcessTag (tagid, attributes, tagtype)) {
382 TextParsed (location, location.PlainText);
383 } else if (stack.Count != count) {
384 CloseControl (tagid);
387 case TagType.DataBinding:
388 goto case TagType.CodeRender;
389 case TagType.CodeRenderExpression:
390 goto case TagType.CodeRender;
391 case TagType.CodeRender:
393 throw new ParseException (location, "Invalid content for application file.");
395 ProcessCode (tagtype, tagid, location);
397 case TagType.Include:
399 throw new ParseException (location, "Invalid content for application file.");
401 string file = attributes ["virtual"] as string;
402 bool isvirtual = (file != null);
404 file = attributes ["file"] as string;
407 file = tparser.MapPath (file);
409 file = GetIncludeFilePath (tparser.BaseDir, file);
417 //PrintLocation (location);
420 static bool TryRemoveTag (string tagid, ArrayList otags)
422 if (otags == null || otags.Count == 0)
425 for (int idx = otags.Count - 1; idx >= 0; idx--) {
426 string otagid = (string) otags [idx];
427 if (0 == String.Compare (tagid, otagid, true, CultureInfo.InvariantCulture)) {
429 otags.RemoveAt (idx);
430 } while (otags.Count - 1 >= idx);
437 static string GetIncludeFilePath (string basedir, string filename)
439 if (Path.DirectorySeparatorChar == '/')
440 filename = filename.Replace ("\\", "/");
442 return Path.GetFullPath (Path.Combine (basedir, filename));
445 void TextParsed (ILocation location, string text)
450 if (text.IndexOf ("<%") != -1 && !inScript) {
451 if (this.text.Length > 0)
453 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
458 this.text.Append (text);
459 //PrintLocation (location);
464 string t = text.ToString ();
467 // TODO: store location
468 tparser.Scripts.Add (t);
472 if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
473 throw new ParseException (location, "Content not valid for application file.");
475 ControlBuilder current = stack.Builder;
476 current.AppendLiteralString (t);
477 if (current.NeedsTagInnerText ()) {
478 tagInnerText.Append (t);
482 bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
485 if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0)
486 throw new ParseException (location, "Invalid tag for application file.");
489 ControlBuilder parent = stack.Builder;
490 ControlBuilder builder = null;
491 Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
492 if (stack.Count > 1) {
494 builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
495 } catch (TypeLoadException e) {
496 throw new ParseException (Location, "Type not found.", e);
497 } catch (Exception e) {
498 throw new ParseException (Location, e.Message, e);
502 if (builder == null && atts != null && atts.IsRunAtServer ()) {
503 string id = htable ["id"] as string;
504 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
505 throw new ParseException (Location, "'" + id + "' is not a valid identifier");
508 builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
509 } catch (TypeLoadException e) {
510 throw new ParseException (Location, "Type not found.", e);
511 } catch (Exception e) {
512 throw new ParseException (Location, e.Message, e);
519 builder.location = location;
520 builder.ID = htable ["id"] as string;
521 if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
523 throw new ParseException (location, "Only one <form> allowed.");
528 if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
529 if (builder is TemplateBuilder) {
532 stack.Push (builder, location);
534 if (!isApplication && builder is ObjectTagBuilder) {
535 ObjectTagBuilder ot = (ObjectTagBuilder) builder;
536 if (ot.Scope != null && ot.Scope != "")
537 throw new ParseException (location, "Scope not allowed here");
539 if (tagtype == TagType.Tag) {
540 stack.Push (builder, location);
545 parent.AppendSubBuilder (builder);
546 builder.CloseControl ();
552 string ReadFile (string filename)
554 string realpath = tparser.MapPath (filename);
555 using (StreamReader sr = new StreamReader (realpath, WebEncoding.FileEncoding)) {
556 string content = sr.ReadToEnd ();
561 bool ProcessScript (TagType tagtype, TagAttributes attributes)
563 if (tagtype != TagType.Close) {
564 if (attributes != null && attributes.IsRunAtServer ()) {
565 CheckLanguage ((string) attributes ["language"]);
566 string src = (string) attributes ["src"];
569 throw new ParseException (Parser,
570 "src cannot be an empty string");
572 string content = ReadFile (src);
574 TextParsed (Parser, content);
577 if (tagtype != TagType.SelfClosing) {
579 Parser.VerbatimID = "script";
581 } else if (tagtype == TagType.Tag) {
582 Parser.VerbatimID = "script";
588 if (tagtype != TagType.SelfClosing) {
589 Parser.VerbatimID = "script";
592 TextParsed (location, location.PlainText);
601 } else if (!ignore_text) {
604 TextParsed (location, location.PlainText);
613 bool CloseControl (string tagid)
615 ControlBuilder current = stack.Builder;
616 string btag = current.TagName;
617 if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
618 String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
619 if (!current.ChildrenAsProperties) {
621 TextParsed (location, location.PlainText);
628 if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture))
631 // if (current is TemplateBuilder)
632 // pop from the id list
633 if (current.NeedsTagInnerText ()) {
635 current.SetTagInnerText (tagInnerText.ToString ());
636 } catch (Exception e) {
637 throw new ParseException (current.location, e.Message, e);
640 tagInnerText.Length = 0;
643 if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
647 current.CloseControl ();
649 stack.Builder.AppendSubBuilder (current);
653 bool ProcessCode (TagType tagtype, string code, ILocation location)
655 ControlBuilder b = null;
656 if (tagtype == TagType.CodeRender)
657 b = new CodeRenderBuilder (code, false, location);
658 else if (tagtype == TagType.CodeRenderExpression)
659 b = new CodeRenderBuilder (code, true, location);
660 else if (tagtype == TagType.DataBinding)
661 b = new DataBindingBuilder (code, location);
663 throw new HttpException ("Should never happen");
665 stack.Builder.AppendSubBuilder (b);
669 public ILocation Location {
670 get { return location; }
673 void CheckLanguage (string lang)
675 if (lang == null || lang == "")
678 if (String.Compare (lang, tparser.Language, true, CultureInfo.InvariantCulture) == 0)
682 CompilationSection section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
683 if (section.Compilers[tparser.Language] != section.Compilers[lang])
685 CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current);
686 if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
688 throw new ParseException (Location,
689 String.Format ("Trying to mix language '{0}' and '{1}'.",
690 tparser.Language, lang));
693 // Used to get CodeRender tags in attribute values
694 class CodeRenderParser
697 ControlBuilder builder;
699 public CodeRenderParser (string str, ControlBuilder builder)
702 this.builder = builder;
705 public void AddChildren ()
707 int index = str.IndexOf ("<%");
709 TextParsed (null, str.Substring (0, index));
710 str = str.Substring (index);
713 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
714 parser.Error += new ParseErrorHandler (ParseError);
715 parser.TagParsed += new TagParsedHandler (TagParsed);
716 parser.TextParsed += new TextParsedHandler (TextParsed);
720 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
722 if (tagtype == TagType.CodeRender)
723 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
724 else if (tagtype == TagType.CodeRenderExpression)
725 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
726 else if (tagtype == TagType.DataBinding)
727 builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
729 builder.AppendLiteralString (location.PlainText);
732 void TextParsed (ILocation location, string text)
734 builder.AppendLiteralString (text);
737 void ParseError (ILocation location, string message)
739 throw new ParseException (location, message);