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;
39 using System.Web.Hosting;
41 using System.Web.UI.HtmlControls;
42 using System.Web.Util;
44 namespace System.Web.Compilation
48 public ControlBuilder Builder;
49 public ILocation Location;
51 public BuilderLocation (ControlBuilder builder, ILocation location)
53 this.Builder = builder;
54 this.Location = location;
58 class BuilderLocationStack : Stack
60 public override void Push (object o)
62 if (!(o is BuilderLocation))
63 throw new InvalidOperationException ();
68 public virtual void Push (ControlBuilder builder, ILocation location)
70 BuilderLocation bl = new BuilderLocation (builder, location);
74 public new BuilderLocation Peek ()
76 return (BuilderLocation) base.Peek ();
79 public new BuilderLocation Pop ()
81 return (BuilderLocation) base.Pop ();
84 public ControlBuilder Builder {
85 get { return Peek ().Builder; }
97 files = new Hashtable (); // may be this should be case sensitive for windows
98 parsers = new Stack ();
101 public bool Push (AspParser parser)
103 if (files.Contains (parser.Filename))
106 files [parser.Filename] = true;
107 parsers.Push (parser);
112 public AspParser Pop ()
114 if (parsers.Count == 0)
117 files.Remove (current.Filename);
118 AspParser result = (AspParser) parsers.Pop ();
119 if (parsers.Count > 0)
120 current = (AspParser) parsers.Peek ();
128 get { return parsers.Count; }
131 public AspParser Parser {
132 get { return current; }
135 public string Filename {
136 get { return current.Filename; }
149 public void Push (string tagid)
159 return (string) tags.Pop ();
162 public bool CompareTo (string tagid)
167 return 0 == String.Compare (tagid, (string) tags.Peek (), true, CultureInfo.InvariantCulture);
171 get { return tags.Count; }
174 public string Current {
175 get { return (string) tags.Peek (); }
182 BuilderLocationStack stack;
183 TemplateParser tparser;
185 RootBuilder rootBuilder;
186 bool inScript, javascript, ignore_text;
189 StringBuilder tagInnerText = new StringBuilder ();
190 static Hashtable emptyHash = new Hashtable ();
195 public AspGenerator (TemplateParser tparser)
197 this.tparser = tparser;
198 text = new StringBuilder ();
199 stack = new BuilderLocationStack ();
200 rootBuilder = new RootBuilder (tparser);
201 stack.Push (rootBuilder, null);
202 tparser.RootBuilder = rootBuilder;
203 pstack = new ParserStack ();
206 public RootBuilder RootBuilder {
207 get { return tparser.RootBuilder; }
210 public AspParser Parser {
211 get { return pstack.Parser; }
214 public string Filename {
215 get { return pstack.Filename; }
218 BaseCompiler GetCompilerFromType ()
220 Type type = tparser.GetType ();
221 if (type == typeof (PageParser))
222 return new PageCompiler ((PageParser) tparser);
224 if (type == typeof (ApplicationFileParser))
225 return new GlobalAsaxCompiler ((ApplicationFileParser) tparser);
227 if (type == typeof (UserControlParser))
228 return new UserControlCompiler ((UserControlParser) tparser);
230 if (type == typeof(MasterPageParser))
231 return new MasterPageCompiler ((MasterPageParser) tparser);
234 throw new Exception ("Got type: " + type);
237 void InitParser (TextReader reader, string filename)
239 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);
246 if (filename != "@@inner_string@@") {
247 string arvp = Path.Combine (tparser.BaseVirtualDir, Path.GetFileName (filename));
248 if (VirtualPathUtility.IsAbsolute (arvp))
249 arvp = VirtualPathUtility.ToAppRelative (arvp);
251 tparser.AddDependency (arvp);
254 tparser.MD5Checksum = parser.MD5Checksum;
259 void InitParser (string filename)
261 StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
262 InitParser (reader, filename);
266 public void Parse (string file)
275 public void Parse (TextReader reader, string filename, bool doInitParser)
278 isApplication = tparser.DefaultDirectiveName == "application";
281 InitParser (reader, filename);
283 pstack.Parser.Parse ();
290 PrintTree (rootBuilder, 0);
293 if (stack.Count > 1 && pstack.Count == 0)
294 throw new ParseException (stack.Builder.location,
295 "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
302 public void Parse (Stream stream, string filename, bool doInitParser)
304 Parse (new StreamReader (stream, WebEncoding.FileEncoding), filename, doInitParser);
307 public void Parse (string filename, bool doInitParser)
309 StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
310 Parse (reader, filename, doInitParser);
316 string inputFile = tparser.InputFile;
317 TextReader inputReader = tparser.Reader;
320 if (String.IsNullOrEmpty (inputFile)) {
321 StreamReader sr = inputReader as StreamReader;
323 FileStream fr = sr.BaseStream as FileStream;
328 if (String.IsNullOrEmpty (inputFile))
329 inputFile = "@@inner_string@@";
332 if (inputReader != null) {
333 Parse (inputReader, inputFile, true);
335 if (String.IsNullOrEmpty (inputFile))
336 throw new HttpException ("Parser input file is empty, cannot continue.");
337 inputFile = Path.GetFullPath (inputFile);
338 InitParser (inputFile);
342 if (inputReader != null)
343 inputReader.Close ();
346 Parse (Path.GetFullPath (tparser.InputFile));
350 internal static void AddTypeToCache (ArrayList dependencies, string inputFile, Type type)
352 string [] deps = (string []) dependencies.ToArray (typeof (string));
353 HttpContext ctx = HttpContext.Current;
354 HttpRequest req = ctx != null ? ctx.Request : null;
357 throw new HttpException ("No current context, cannot compile.");
359 for (int i = 0; i < deps.Length; i++)
360 deps [i] = req.MapPath (deps [i]);
362 HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type, new CacheDependency (deps));
365 public Type GetCompiledType ()
367 Type type = (Type) HttpRuntime.InternalCache.Get ("@@Type" + tparser.InputFile);
374 BaseCompiler compiler = GetCompilerFromType ();
376 type = compiler.GetCompiledType ();
377 AddTypeToCache (tparser.Dependencies, tparser.InputFile, type);
382 static void PrintTree (ControlBuilder builder, int indent)
387 string i = new string ('\t', indent);
389 Console.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
390 builder, builder.ID, builder.ControlType, builder.parentBuilder);
392 if (builder.Children != null)
393 foreach (object o in builder.Children) {
394 if (o is ControlBuilder)
395 PrintTree ((ControlBuilder) o, indent++);
399 static void PrintLocation (ILocation loc)
401 Console.WriteLine ("\tFile name: " + loc.Filename);
402 Console.WriteLine ("\tBegin line: " + loc.BeginLine);
403 Console.WriteLine ("\tEnd line: " + loc.EndLine);
404 Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
405 Console.WriteLine ("\tEnd column: " + loc.EndColumn);
406 Console.WriteLine ("\tPlainText: " + loc.PlainText);
407 Console.WriteLine ();
411 void ParseError (ILocation location, string message)
413 throw new ParseException (location, message);
416 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
418 this.location = new Location (location);
420 tparser.Location = location;
422 if (text.Length != 0)
423 FlushText (lastTag == TagType.CodeRender);
425 if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) {
426 bool in_script = (inScript || ignore_text);
427 if (in_script || (tagtype != TagType.Close && attributes != null)) {
428 if ((in_script || attributes.IsRunAtServer ()) && ProcessScript (tagtype, attributes))
435 case TagType.Directive:
437 tagid = tparser.DefaultDirectiveName;
439 tparser.AddDirective (tagid, attributes.GetDictionary (null));
442 if (ProcessTag (tagid, attributes, tagtype)) {
448 stack.Builder.EnsureOtherTags ();
449 stack.Builder.OtherTags.Add (tagid);
452 TextParsed (location, location.PlainText);
455 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
456 if (!notServer && CloseControl (tagid))
459 TextParsed (location, location.PlainText);
461 case TagType.SelfClosing:
462 int count = stack.Count;
463 if (!ProcessTag (tagid, attributes, tagtype)) {
464 TextParsed (location, location.PlainText);
465 } else if (stack.Count != count) {
466 CloseControl (tagid);
469 case TagType.DataBinding:
470 goto case TagType.CodeRender;
471 case TagType.CodeRenderExpression:
472 goto case TagType.CodeRender;
473 case TagType.CodeRender:
474 lastTag = TagType.CodeRender;
476 throw new ParseException (location, "Invalid content for application file.");
478 ProcessCode (tagtype, tagid, location);
480 case TagType.Include:
482 throw new ParseException (location, "Invalid content for application file.");
484 string file = attributes ["virtual"] as string;
485 bool isvirtual = (file != null);
487 file = attributes ["file"] as string;
492 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
494 if (vpp.FileExists (file)) {
495 VirtualFile vf = vpp.GetFile (file);
497 Parse (vf.Open (), file, true);
504 Parse (tparser.MapPath (file), true);
506 string includeFilePath = GetIncludeFilePath (tparser.ParserDir, file);
507 tparser.PushIncludeDir (Path.GetDirectoryName (includeFilePath));
509 Parse (includeFilePath, true);
511 tparser.PopIncludeDir ();
519 //PrintLocation (location);
522 static bool TryRemoveTag (string tagid, ArrayList otags)
524 if (otags == null || otags.Count == 0)
527 for (int idx = otags.Count - 1; idx >= 0; idx--) {
528 string otagid = (string) otags [idx];
529 if (0 == String.Compare (tagid, otagid, true, CultureInfo.InvariantCulture)) {
531 otags.RemoveAt (idx);
532 } while (otags.Count - 1 >= idx);
539 static string GetIncludeFilePath (string basedir, string filename)
541 if (Path.DirectorySeparatorChar == '/')
542 filename = filename.Replace ("\\", "/");
544 return Path.GetFullPath (Path.Combine (basedir, filename));
547 void TextParsed (ILocation location, string text)
552 if (text.IndexOf ("<%") != -1 && !inScript) {
553 if (this.text.Length > 0)
555 CodeRenderParser r = new CodeRenderParser (text, stack.Builder);
560 this.text.Append (text);
561 //PrintLocation (location);
569 void FlushText (bool ignoreEmptyString)
571 string t = text.ToString ();
574 if (ignoreEmptyString && t.Trim ().Length == 0)
578 tparser.Scripts.Add (new ServerSideScript (t, new System.Web.Compilation.Location (tparser.Location)));
582 if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
583 throw new ParseException (location, "Content not valid for application file.");
585 ControlBuilder current = stack.Builder;
586 current.AppendLiteralString (t);
587 if (current.NeedsTagInnerText ()) {
588 tagInnerText.Append (t);
593 bool BuilderHasOtherThan (Type type, ControlBuilder cb)
595 ArrayList al = cb.OtherTags;
596 if (al != null && al.Count > 0)
603 foreach (object o in al) {
607 tmp = o as ControlBuilder;
609 string s = o as string;
610 if (s != null && String.IsNullOrEmpty (s.Trim ()))
616 if (tmp is System.Web.UI.WebControls.ContentBuilderInternal)
619 if (tmp.ControlType != typeof (System.Web.UI.WebControls.Content))
627 bool OtherControlsAllowed (ControlBuilder cb)
632 if (!typeof (System.Web.UI.WebControls.Content).IsAssignableFrom (cb.ControlType))
635 if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), rootBuilder))
642 bool ProcessTag (string tagid, TagAttributes atts, TagType tagtype)
645 if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0)
646 throw new ParseException (location, "Invalid tag for application file.");
649 ControlBuilder parent = stack.Builder;
650 ControlBuilder builder = null;
651 Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
652 if (stack.Count > 1) {
654 builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
655 } catch (TypeLoadException e) {
656 throw new ParseException (Location, "Type not found.", e);
657 } catch (Exception e) {
658 throw new ParseException (Location, e.Message, e);
662 if (builder == null && atts != null && atts.IsRunAtServer ()) {
663 string id = htable ["id"] as string;
664 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
665 throw new ParseException (Location, "'" + id + "' is not a valid identifier");
668 builder = rootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
669 } catch (TypeLoadException e) {
670 throw new ParseException (Location, "Type not found.", e);
671 } catch (Exception e) {
672 throw new ParseException (Location, e.Message, e);
680 if (!OtherControlsAllowed (builder))
681 throw new ParseException (Location, "Only Content controls are allowed directly in a content page that contains Content controls.");
684 builder.location = location;
685 builder.ID = htable ["id"] as string;
686 if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
688 throw new ParseException (location, "Only one <form> allowed.");
693 if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
694 if (builder is TemplateBuilder) {
697 stack.Push (builder, location);
699 if (!isApplication && builder is ObjectTagBuilder) {
700 ObjectTagBuilder ot = (ObjectTagBuilder) builder;
701 if (ot.Scope != null && ot.Scope != "")
702 throw new ParseException (location, "Scope not allowed here");
704 if (tagtype == TagType.Tag) {
705 stack.Push (builder, location);
710 parent.AppendSubBuilder (builder);
711 builder.CloseControl ();
717 string ReadFile (string filename)
719 string realpath = tparser.MapPath (filename);
720 using (StreamReader sr = new StreamReader (realpath, WebEncoding.FileEncoding)) {
721 string content = sr.ReadToEnd ();
726 bool ProcessScript (TagType tagtype, TagAttributes attributes)
728 if (tagtype != TagType.Close) {
729 if (attributes != null && attributes.IsRunAtServer ()) {
730 string language = (string) attributes ["language"];
731 if (language != null && language.Length > 0 && tparser.ImplicitLanguage)
732 tparser.SetLanguage (language);
733 CheckLanguage (language);
734 string src = (string) attributes ["src"];
737 throw new ParseException (Parser,
738 "src cannot be an empty string");
740 string content = ReadFile (src);
742 TextParsed (Parser, content);
745 if (tagtype != TagType.SelfClosing) {
747 Parser.VerbatimID = "script";
749 } else if (tagtype == TagType.Tag) {
750 Parser.VerbatimID = "script";
756 if (tagtype != TagType.SelfClosing) {
757 Parser.VerbatimID = "script";
760 TextParsed (location, location.PlainText);
769 } else if (!ignore_text) {
772 TextParsed (location, location.PlainText);
781 bool CloseControl (string tagid)
783 ControlBuilder current = stack.Builder;
784 string btag = current.TagName;
785 if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
786 String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
787 if (!current.ChildrenAsProperties) {
789 TextParsed (location, location.PlainText);
796 if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture))
799 // if (current is TemplateBuilder)
800 // pop from the id list
801 if (current.NeedsTagInnerText ()) {
803 current.SetTagInnerText (tagInnerText.ToString ());
804 } catch (Exception e) {
805 throw new ParseException (current.location, e.Message, e);
808 tagInnerText.Length = 0;
811 if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
815 current.CloseControl ();
817 stack.Builder.AppendSubBuilder (current);
821 bool ProcessCode (TagType tagtype, string code, ILocation location)
823 ControlBuilder b = null;
824 if (tagtype == TagType.CodeRender)
825 b = new CodeRenderBuilder (code, false, location);
826 else if (tagtype == TagType.CodeRenderExpression)
827 b = new CodeRenderBuilder (code, true, location);
828 else if (tagtype == TagType.DataBinding)
829 b = new DataBindingBuilder (code, location);
831 throw new HttpException ("Should never happen");
833 stack.Builder.AppendSubBuilder (b);
837 public ILocation Location {
838 get { return location; }
841 void CheckLanguage (string lang)
843 if (lang == null || lang == "")
846 if (String.Compare (lang, tparser.Language, true, CultureInfo.InvariantCulture) == 0)
850 CompilationSection section = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
851 if (section.Compilers[tparser.Language] != section.Compilers[lang])
853 CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current);
854 if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
856 throw new ParseException (Location,
857 String.Format ("Trying to mix language '{0}' and '{1}'.",
858 tparser.Language, lang));
861 // Used to get CodeRender tags in attribute values
862 class CodeRenderParser
865 ControlBuilder builder;
867 public CodeRenderParser (string str, ControlBuilder builder)
870 this.builder = builder;
873 public void AddChildren ()
875 int index = str.IndexOf ("<%");
877 TextParsed (null, str.Substring (0, index));
878 str = str.Substring (index);
881 AspParser parser = new AspParser ("@@inner_string@@", new StringReader (str));
882 parser.Error += new ParseErrorHandler (ParseError);
883 parser.TagParsed += new TagParsedHandler (TagParsed);
884 parser.TextParsed += new TextParsedHandler (TextParsed);
888 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
890 if (tagtype == TagType.CodeRender)
891 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
892 else if (tagtype == TagType.CodeRenderExpression)
893 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
894 else if (tagtype == TagType.DataBinding)
895 builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
897 builder.AppendLiteralString (location.PlainText);
900 void TextParsed (ILocation location, string text)
902 builder.AppendLiteralString (text);
905 void ParseError (ILocation location, string message)
907 throw new ParseException (location, message);