2 // System.Web.Compilation.AspGenerator
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Marek Habersack <mhabersack@novell.com>
8 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
9 // Copyright (c) 2004-2009 Novell, Inc (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.CodeDom.Compiler;
35 using System.Globalization;
38 using System.Text.RegularExpressions;
39 using System.Web.Caching;
40 using System.Web.Configuration;
41 using System.Web.Hosting;
43 using System.Web.UI.HtmlControls;
44 using System.Web.Util;
47 using System.Collections.Generic;
50 namespace System.Web.Compilation
54 public ControlBuilder Builder;
55 public ILocation Location;
57 public BuilderLocation (ControlBuilder builder, ILocation location)
59 this.Builder = builder;
60 this.Location = new Location (location);
64 class BuilderLocationStack : Stack
66 public override void Push (object o)
68 if (!(o is BuilderLocation))
69 throw new InvalidOperationException ();
74 public virtual void Push (ControlBuilder builder, ILocation location)
76 BuilderLocation bl = new BuilderLocation (builder, location);
80 public new BuilderLocation Peek ()
82 return (BuilderLocation) base.Peek ();
85 public new BuilderLocation Pop ()
87 return (BuilderLocation) base.Pop ();
90 public ControlBuilder Builder {
91 get { return Peek ().Builder; }
101 public ParserStack ()
103 files = new Hashtable (); // may be this should be case sensitive for windows
104 parsers = new Stack ();
107 public bool Push (AspParser parser)
109 if (files.Contains (parser.Filename))
112 files [parser.Filename] = true;
113 parsers.Push (parser);
118 public AspParser Pop ()
120 if (parsers.Count == 0)
123 files.Remove (current.Filename);
124 AspParser result = (AspParser) parsers.Pop ();
125 if (parsers.Count > 0)
126 current = (AspParser) parsers.Peek ();
134 get { return parsers.Count; }
137 public AspParser Parser {
138 get { return current; }
141 public string Filename {
142 get { return current.Filename; }
155 public void Push (string tagid)
165 return (string) tags.Pop ();
168 public bool CompareTo (string tagid)
173 return 0 == String.Compare (tagid, (string) tags.Peek (), true, CultureInfo.InvariantCulture);
177 get { return tags.Count; }
180 public string Current {
181 get { return (string) tags.Peek (); }
193 sealed class TextBlock
195 public string Content;
196 public readonly TextBlockType Type;
197 public readonly int Length;
199 public TextBlock (TextBlockType type, string content)
203 Length = content.Length;
206 public override string ToString ()
208 return this.GetType ().FullName + " [" + this.Type + "]";
215 const int READ_BUFFER_SIZE = 8192;
217 internal static Regex DirectiveRegex = new Regex (@"<%\s*@(\s*(?<attrname>\w[\w:]*(?=\W))(\s*(?<equal>=)\s*""(?<attrval>[^""]*)""|\s*(?<equal>=)\s*'(?<attrval>[^']*)'|\s*(?<equal>=)\s*(?<attrval>[^\s%>]*)|(?<equal>)(?<attrval>\s*?)))*\s*?%>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
219 static readonly Regex runatServer = new Regex (@"<[\w:\.]+.*?runat=[""']?server[""']?.*?/?>",
220 RegexOptions.Compiled | RegexOptions.Singleline |
221 RegexOptions.Multiline | RegexOptions.IgnoreCase |
222 RegexOptions.CultureInvariant);
224 static readonly Regex endOfTag = new Regex (@"</[\w:\.]+\s*?>",
225 RegexOptions.Compiled | RegexOptions.Singleline |
226 RegexOptions.Multiline | RegexOptions.IgnoreCase |
227 RegexOptions.CultureInvariant);
229 static readonly Regex expressionRegex = new Regex (@"<%.*?%>",
230 RegexOptions.Compiled | RegexOptions.Singleline |
231 RegexOptions.Multiline | RegexOptions.IgnoreCase |
232 RegexOptions.CultureInvariant);
234 static readonly Regex clientCommentRegex = new Regex (@"<!--(.|\s)*?-->",
235 RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase |
236 RegexOptions.CultureInvariant);
239 BuilderLocationStack stack;
240 TemplateParser tparser;
242 RootBuilder rootBuilder;
243 bool inScript, javascript, ignore_text;
246 StringBuilder tagInnerText = new StringBuilder ();
247 static Hashtable emptyHash = new Hashtable ();
252 AspComponentFoundry componentFoundry;
255 public AspGenerator (TemplateParser tparser, AspComponentFoundry componentFoundry) : this (tparser)
257 this.componentFoundry = componentFoundry;
261 public AspGenerator (TemplateParser tparser)
263 this.tparser = tparser;
264 text = new StringBuilder ();
265 stack = new BuilderLocationStack ();
268 rootBuilder = new RootBuilder (tparser);
269 tparser.RootBuilder = rootBuilder;
270 stack.Push (rootBuilder, null);
272 pstack = new ParserStack ();
275 public RootBuilder RootBuilder {
276 get { return rootBuilder; }
279 public AspParser Parser {
280 get { return pstack.Parser; }
283 public string Filename {
284 get { return pstack.Filename; }
288 PageParserFilter PageParserFilter {
293 return tparser.PageParserFilter;
299 // The kludge to determine the base type of the to-be-generated ASP.NET class is
300 // very unfortunate but with our current parser it is, unfortunately, necessary. The
301 // reason for reading the entire file into memory and parsing it with a regexp is
302 // that we need to read the main directive (i.e. <%@Page %>, <%@Control %> etc),
303 // pass it to the page parser filter if it exists, and finally read the inherits
304 // attribute of the directive to get access to the base type of the class to be
305 // generated. On that type we check whether it is decorated with the
306 // FileLevelControlBuilder attribute and, if yes, use the indicated type as the
307 // RootBuilder. This is necessary for the ASP.NET MVC views using the "generic"
308 // inherits declaration to work properly. Our current parser is not able to parse
309 // the input file out of sequence (i.e. directives first, then the rest) so we need
310 // to do what we do below, alas.
311 Hashtable GetDirectiveAttributesDictionary (string skipKeyName, CaptureCollection names, CaptureCollection values)
313 var ret = new Hashtable (StringComparer.InvariantCultureIgnoreCase);
317 foreach (Capture c in names) {
319 if (String.Compare (skipKeyName, keyName, StringComparison.OrdinalIgnoreCase) == 0) {
324 ret.Add (c.Value, values [index++].Value);
330 string GetDirectiveName (CaptureCollection names)
333 foreach (Capture c in names) {
335 if (Directive.IsDirective (val))
339 return tparser.DefaultDirectiveName;
342 int GetLineNumberForIndex (string fileContents, int index)
346 bool foundCR = false;
348 for (int pos = 0; pos < index; pos++) {
349 c = fileContents [pos];
350 if (c == '\n' || foundCR) {
355 foundCR = (c == '\r');
361 int GetNumberOfLinesForRange (string fileContents, int index, int length)
364 int stop = index + length;
366 bool foundCR = false;
368 for (int pos = index; pos < stop; pos++) {
369 c = fileContents [pos];
370 if (c == '\n' || foundCR) {
375 foundCR = (c == '\r');
381 Type GetInheritedType (string fileContents, string filename)
383 MatchCollection matches = DirectiveRegex.Matches (fileContents);
384 if (matches == null || matches.Count == 0)
387 string wantedDirectiveName = tparser.DefaultDirectiveName.ToLower ();
388 string directiveName;
389 GroupCollection groups;
390 CaptureCollection ccNames;
392 foreach (Match match in matches) {
393 groups = match.Groups;
394 if (groups.Count < 6)
397 ccNames = groups [3].Captures;
398 directiveName = GetDirectiveName (ccNames);
399 if (String.IsNullOrEmpty (directiveName))
402 if (String.Compare (directiveName.ToLower (), wantedDirectiveName, StringComparison.Ordinal) != 0)
405 var loc = new Location (null);
406 int index = match.Index;
408 loc.Filename = filename;
409 loc.BeginLine = GetLineNumberForIndex (fileContents, index);
410 loc.EndLine = loc.BeginLine + GetNumberOfLinesForRange (fileContents, index, match.Length);
412 tparser.Location = loc;
413 tparser.allowedMainDirectives = 2;
414 tparser.AddDirective (wantedDirectiveName, GetDirectiveAttributesDictionary (wantedDirectiveName, ccNames, groups [5].Captures));
416 return tparser.BaseType;
422 string ReadFileContents (Stream inputStream, string filename)
426 if (inputStream != null) {
427 if (inputStream.CanSeek) {
428 long curPos = inputStream.Position;
429 inputStream.Seek (0, SeekOrigin.Begin);
431 Encoding enc = WebEncoding.FileEncoding;
432 StringBuilder sb = new StringBuilder ();
433 byte[] buffer = new byte [READ_BUFFER_SIZE];
436 while ((nbytes = inputStream.Read (buffer, 0, READ_BUFFER_SIZE)) > 0)
437 sb.Append (enc.GetString (buffer, 0, nbytes));
438 inputStream.Seek (curPos, SeekOrigin.Begin);
440 ret = sb.ToString ();
444 FileStream fs = inputStream as FileStream;
446 string fname = fs.Name;
448 if (File.Exists (fname))
449 ret = File.ReadAllText (fname);
457 if (ret == null && !String.IsNullOrEmpty (filename) && String.Compare (filename, "@@inner_string@@", StringComparison.Ordinal) != 0) {
459 if (File.Exists (filename))
460 ret = File.ReadAllText (filename);
469 Type GetRootBuilderType (Stream inputStream, string filename)
475 fileContents = ReadFileContents (inputStream, filename);
479 if (!String.IsNullOrEmpty (fileContents)) {
480 Type inheritedType = GetInheritedType (fileContents, filename);
482 if (inheritedType != null) {
483 FileLevelControlBuilderAttribute attr;
486 object[] attrs = inheritedType.GetCustomAttributes (typeof (FileLevelControlBuilderAttribute), true);
487 if (attrs != null && attrs.Length > 0)
488 attr = attrs [0] as FileLevelControlBuilderAttribute;
495 ret = attr != null ? attr.BuilderType : null;
500 if (tparser is PageParser)
501 return typeof (FileLevelPageControlBuilder);
502 else if (tparser is UserControlParser)
503 return typeof (FileLevelUserControlBuilder);
505 return typeof (RootBuilder);
510 void CreateRootBuilder (Stream inputStream, string filename)
512 if (rootBuilder != null)
515 Type rootBuilderType = GetRootBuilderType (inputStream, filename);
516 rootBuilder = Activator.CreateInstance (rootBuilderType) as RootBuilder;
517 if (rootBuilder == null)
518 throw new HttpException ("Cannot create an instance of file-level control builder.");
519 rootBuilder.Init (tparser, null, null, null, null, null);
520 if (componentFoundry != null)
521 rootBuilder.Foundry = componentFoundry;
523 stack.Push (rootBuilder, null);
524 tparser.RootBuilder = rootBuilder;
528 BaseCompiler GetCompilerFromType ()
530 Type type = tparser.GetType ();
531 if (type == typeof (PageParser))
532 return new PageCompiler ((PageParser) tparser);
534 if (type == typeof (ApplicationFileParser))
535 return new GlobalAsaxCompiler ((ApplicationFileParser) tparser);
537 if (type == typeof (UserControlParser))
538 return new UserControlCompiler ((UserControlParser) tparser);
540 if (type == typeof(MasterPageParser))
541 return new MasterPageCompiler ((MasterPageParser) tparser);
544 throw new Exception ("Got type: " + type);
547 void InitParser (TextReader reader, string filename)
549 AspParser parser = new AspParser (filename, reader);
550 parser.Error += new ParseErrorHandler (ParseError);
551 parser.TagParsed += new TagParsedHandler (TagParsed);
552 parser.TextParsed += new TextParsedHandler (TextParsed);
554 parser.ParsingComplete += new ParsingCompleteHandler (ParsingCompleted);
555 tparser.AspGenerator = this;
556 CreateRootBuilder (inputStream, filename);
558 if (!pstack.Push (parser))
559 throw new ParseException (Location, "Infinite recursion detected including file: " + filename);
561 if (filename != "@@inner_string@@") {
562 string arvp = Path.Combine (tparser.BaseVirtualDir, Path.GetFileName (filename));
563 if (VirtualPathUtility.IsAbsolute (arvp))
564 arvp = VirtualPathUtility.ToAppRelative (arvp);
566 tparser.AddDependency (arvp);
571 void InitParser (string filename)
573 StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
574 InitParser (reader, filename);
578 void CheckForDuplicateIds (ControlBuilder root, Stack scopes)
584 scopes = new Stack ();
587 Dictionary <string, bool> ids;
592 if (scopes.Count == 0 || root.IsNamingContainer) {
594 ids = new Dictionary <string, bool> (StringComparer.Ordinal);
596 ids = new Hashtable ();
601 ids = scopes.Peek () as Dictionary <string, bool>;
603 ids = scopes.Peek () as Hashtable;
612 ArrayList children = root.Children;
613 if (children != null) {
614 foreach (object o in children) {
615 cb = o as ControlBuilder;
620 if (id == null || id.Length == 0)
623 if (ids.ContainsKey (id))
624 throw new ParseException (cb.Location, "Id '" + id + "' is already used by another control.");
627 CheckForDuplicateIds (cb, scopes);
632 public void Parse (string file)
641 public void Parse (TextReader reader, string filename, bool doInitParser)
644 isApplication = tparser.DefaultDirectiveName == "application";
647 InitParser (reader, filename);
649 pstack.Parser.Parse ();
654 tparser.MD5Checksum = pstack.Parser.MD5Checksum;
659 PrintTree (RootBuilder, 0);
662 if (stack.Count > 1 && pstack.Count == 0)
663 throw new ParseException (stack.Builder.Location,
664 "Expecting </" + stack.Builder.TagName + "> " + stack.Builder);
666 CheckForDuplicateIds (RootBuilder, null);
673 public void Parse (Stream stream, string filename, bool doInitParser)
676 inputStream = stream;
678 Parse (new StreamReader (stream, WebEncoding.FileEncoding), filename, doInitParser);
681 public void Parse (string filename, bool doInitParser)
683 StreamReader reader = new StreamReader (filename, WebEncoding.FileEncoding);
684 Parse (reader, filename, doInitParser);
690 string inputFile = tparser.InputFile;
691 TextReader inputReader = tparser.Reader;
694 if (String.IsNullOrEmpty (inputFile)) {
695 StreamReader sr = inputReader as StreamReader;
697 FileStream fr = sr.BaseStream as FileStream;
702 if (String.IsNullOrEmpty (inputFile))
703 inputFile = "@@inner_string@@";
706 if (inputReader != null) {
707 Parse (inputReader, inputFile, true);
709 if (String.IsNullOrEmpty (inputFile))
710 throw new HttpException ("Parser input file is empty, cannot continue.");
711 inputFile = Path.GetFullPath (inputFile);
712 InitParser (inputFile);
716 if (inputReader != null)
717 inputReader.Close ();
720 Parse (Path.GetFullPath (tparser.InputFile));
724 internal static void AddTypeToCache (ArrayList dependencies, string inputFile, Type type)
726 if (type == null || inputFile == null || inputFile.Length == 0)
729 if (dependencies != null && dependencies.Count > 0) {
730 string [] deps = (string []) dependencies.ToArray (typeof (string));
731 HttpContext ctx = HttpContext.Current;
732 HttpRequest req = ctx != null ? ctx.Request : null;
735 throw new HttpException ("No current context, cannot compile.");
737 for (int i = 0; i < deps.Length; i++)
738 deps [i] = req.MapPath (deps [i]);
740 HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type, new CacheDependency (deps));
742 HttpRuntime.InternalCache.Insert ("@@Type" + inputFile, type);
745 public Type GetCompiledType ()
747 Type type = (Type) HttpRuntime.InternalCache.Get ("@@Type" + tparser.InputFile);
754 BaseCompiler compiler = GetCompilerFromType ();
756 type = compiler.GetCompiledType ();
757 AddTypeToCache (tparser.Dependencies, tparser.InputFile, type);
762 static void PrintTree (ControlBuilder builder, int indent)
767 string i = new string ('\t', indent);
769 Console.WriteLine ("b: {0}; naming container: {1}; id: {2}; type: {3}; parent: {4}",
770 builder, builder.IsNamingContainer, builder.ID, builder.ControlType, builder.ParentBuilder);
772 if (builder.Children != null)
773 foreach (object o in builder.Children) {
774 if (o is ControlBuilder)
775 PrintTree ((ControlBuilder) o, indent++);
779 static void PrintLocation (ILocation loc)
781 Console.WriteLine ("\tFile name: " + loc.Filename);
782 Console.WriteLine ("\tBegin line: " + loc.BeginLine);
783 Console.WriteLine ("\tEnd line: " + loc.EndLine);
784 Console.WriteLine ("\tBegin column: " + loc.BeginColumn);
785 Console.WriteLine ("\tEnd column: " + loc.EndColumn);
786 Console.WriteLine ("\tPlainText: " + loc.PlainText);
787 Console.WriteLine ();
791 void ParseError (ILocation location, string message)
793 throw new ParseException (location, message);
798 // The code below (ProcessTagsInAttributes, ParseAttributeTag) serves the purpose to work
799 // around a limitation of the current asp.net parser which is unable to parse server
800 // controls inside client tag attributes. Since the architecture of the current
801 // parser does not allow for clean solution of this problem, hence the kludge
802 // below. It will be gone as soon as the parser is rewritten.
804 // The kludge supports only self-closing tags inside attributes.
807 bool ProcessTagsInAttributes (ILocation location, string tagid, TagAttributes attributes, TagType type)
809 if (attributes == null || attributes.Count == 0)
817 StringBuilder sb = new StringBuilder ();
819 sb.AppendFormat ("\t<{0}", tagid);
820 foreach (string key in attributes.Keys) {
821 value = attributes [key] as string;
822 if (value == null || value.Length < 16) { // optimization
823 sb.AppendFormat (" {0}=\"{1}\"", key, value);
827 match = runatServer.Match (attributes [key] as string);
828 if (!match.Success) {
829 sb.AppendFormat (" {0}=\"{1}\"", key, value);
833 TextParsed (location, sb.ToString ());
838 group = match.Groups [0];
840 length = group.Length;
842 TextParsed (location, String.Format (" {0}=\"{1}", key, index > 0 ? value.Substring (0, index) : String.Empty));;
844 ParseAttributeTag (group.Value, location);
845 if (index + length < value.Length)
846 TextParsed (location, value.Substring (index + length) + "\"");
848 TextParsed (location, "\"");
850 if (type == TagType.SelfClosing)
855 if (retval && sb.Length > 0)
856 TextParsed (location, sb.ToString ());
861 void ParseAttributeTag (string code, ILocation location)
863 AspParser parser = new AspParser ("@@attribute_tag@@", new StringReader (code), location.BeginLine - 1, location as AspParser);
864 parser.Error += new ParseErrorHandler (ParseError);
865 parser.TagParsed += new TagParsedHandler (TagParsed);
866 parser.TextParsed += new TextParsedHandler (TextParsed);
873 void ParsingCompleted ()
875 PageParserFilter pfilter = PageParserFilter;
879 pfilter.ParseComplete (RootBuilder);
883 void CheckIfIncludeFileIsSecure (string filePath)
885 if (filePath == null || filePath.Length == 0)
888 // a bit slow, but fully portable
889 string newdir = null;
890 Exception exception = null;
892 string origdir = Directory.GetCurrentDirectory ();
893 Directory.SetCurrentDirectory (Path.GetDirectoryName (filePath));
894 newdir = Directory.GetCurrentDirectory ();
895 Directory.SetCurrentDirectory (origdir);
896 if (newdir [newdir.Length - 1] != '/')
898 } catch (DirectoryNotFoundException) {
899 return; // will be converted into 404
900 } catch (FileNotFoundException) {
902 } catch (Exception ex) {
903 // better safe than sorry
907 if (exception != null || !StrUtils.StartsWith (newdir, HttpRuntime.AppDomainAppPath))
908 throw new ParseException (Location, "Files above the application's root directory cannot be included.");
911 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
915 this.location = new Location (location);
917 tparser.Location = location;
919 if (text.Length != 0)
920 FlushText (lastTag == TagType.CodeRender);
922 if (0 == String.Compare (tagid, "script", true, CultureInfo.InvariantCulture)) {
923 bool in_script = (inScript || ignore_text);
925 if (ProcessScript (tagtype, attributes))
928 if (ProcessScript (tagtype, attributes))
934 case TagType.Directive:
935 if (tagid.Length == 0)
936 tagid = tparser.DefaultDirectiveName;
938 tparser.AddDirective (tagid, attributes.GetDictionary (null));
941 if (ProcessTag (location, tagid, attributes, tagtype, out tagIgnored)) {
948 stack.Builder.EnsureOtherTags ();
949 stack.Builder.OtherTags.Add (tagid);
953 string plainText = location.PlainText;
954 if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.Tag))
955 TextParsed (location, plainText);
959 bool notServer = (useOtherTags && TryRemoveTag (tagid, stack.Builder.OtherTags));
960 if (!notServer && CloseControl (tagid))
963 TextParsed (location, location.PlainText);
965 case TagType.SelfClosing:
966 int count = stack.Count;
967 if (!ProcessTag (location, tagid, attributes, tagtype, out tagIgnored) && !tagIgnored) {
968 string plainText = location.PlainText;
969 if (!ProcessTagsInAttributes (location, tagid, attributes, TagType.SelfClosing))
970 TextParsed (location, plainText);
971 } else if (stack.Count != count) {
972 CloseControl (tagid);
975 case TagType.DataBinding:
976 goto case TagType.CodeRender;
977 case TagType.CodeRenderExpression:
978 goto case TagType.CodeRender;
979 case TagType.CodeRender:
981 throw new ParseException (location, "Invalid content for application file.");
983 ProcessCode (tagtype, tagid, location);
985 case TagType.Include:
987 throw new ParseException (location, "Invalid content for application file.");
989 string file = attributes ["virtual"] as string;
990 bool isvirtual = (file != null);
992 file = attributes ["file"] as string;
997 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
999 if (vpp.FileExists (file)) {
1000 VirtualFile vf = vpp.GetFile (file);
1002 Parse (vf.Open (), file, true);
1009 Parse (tparser.MapPath (file), true);
1011 string includeFilePath = GetIncludeFilePath (tparser.ParserDir, file);
1012 CheckIfIncludeFileIsSecure (includeFilePath);
1013 tparser.PushIncludeDir (Path.GetDirectoryName (includeFilePath));
1015 Parse (includeFilePath, true);
1017 tparser.PopIncludeDir ();
1025 //PrintLocation (location);
1028 static bool TryRemoveTag (string tagid, ArrayList otags)
1030 if (otags == null || otags.Count == 0)
1033 for (int idx = otags.Count - 1; idx >= 0; idx--) {
1034 string otagid = (string) otags [idx];
1035 if (0 == String.Compare (tagid, otagid, true, CultureInfo.InvariantCulture)) {
1037 otags.RemoveAt (idx);
1038 } while (otags.Count - 1 >= idx);
1045 static string GetIncludeFilePath (string basedir, string filename)
1047 if (Path.DirectorySeparatorChar == '/')
1048 filename = filename.Replace ("\\", "/");
1050 return Path.GetFullPath (Path.Combine (basedir, filename));
1053 delegate bool CheckBlockEnd (string text);
1055 bool CheckTagEndNeeded (string text)
1057 return !text.EndsWith ("/>");
1065 FindRegexBlocks (Regex rxStart, Regex rxEnd, CheckBlockEnd checkEnd, IList blocks, TextBlockType typeForMatches, bool discardBlocks)
1068 var ret = new List <TextBlock> ();
1070 ArrayList ret = new ArrayList ();
1073 foreach (TextBlock block in blocks) {
1074 if (block.Type != TextBlockType.Verbatim) {
1079 int lastIndex = 0, index;
1080 MatchCollection matches = rxStart.Matches (block.Content);
1081 bool foundMatches = matches.Count > 0;
1082 foreach (Match match in matches) {
1083 foundMatches = true;
1084 index = match.Index;
1085 if (lastIndex < index)
1086 ret.Add (new TextBlock (TextBlockType.Verbatim, block.Content.Substring (lastIndex, index - lastIndex)));
1088 string value = match.Value;
1089 if (rxEnd != null && checkEnd (value)) {
1090 int startFrom = index + value.Length;
1091 Match m = rxEnd.Match (block.Content, startFrom);
1093 value += block.Content.Substring (startFrom, m.Index - startFrom) + m.Value;
1097 ret.Add (new TextBlock (typeForMatches, value));
1098 lastIndex = index + value.Length;
1101 if (lastIndex > 0 && lastIndex < block.Content.Length)
1102 ret.Add (new TextBlock (TextBlockType.Verbatim, block.Content.Substring (lastIndex)));
1111 IList SplitTextIntoBlocks (string text)
1114 var ret = new List <TextBlock> ();
1116 ArrayList ret = new ArrayList ();
1119 ret.Add (new TextBlock (TextBlockType.Verbatim, text));
1120 ret = FindRegexBlocks (clientCommentRegex, null, null, ret, TextBlockType.Comment, false);
1121 ret = FindRegexBlocks (runatServer, endOfTag, CheckTagEndNeeded, ret, TextBlockType.Tag, false);
1122 ret = FindRegexBlocks (expressionRegex, null, null, ret, TextBlockType.Expression, false);
1127 void TextParsed (ILocation location, string text)
1133 this.text.Append (text);
1138 IList blocks = SplitTextIntoBlocks (text);
1139 foreach (TextBlock block in blocks) {
1140 switch (block.Type) {
1141 case TextBlockType.Verbatim:
1142 this.text.Append (block.Content);
1145 case TextBlockType.Expression:
1146 if (this.text.Length > 0)
1148 CodeRenderParser r = new CodeRenderParser (block.Content, stack.Builder, location);
1149 r.AddChildren (this);
1152 case TextBlockType.Tag:
1153 ParseAttributeTag (block.Content, location);
1156 case TextBlockType.Comment: {
1157 this.text.Append ("<!--");
1159 AspParser parser = new AspParser ("@@comment_code@@",
1160 new StringReader (block.Content.Substring (4, block.Length - 7)),
1161 location.BeginLine - 1,
1162 location as AspParser);
1163 parser.Error += new ParseErrorHandler (ParseError);
1164 parser.TagParsed += new TagParsedHandler (TagParsed);
1165 parser.TextParsed += new TextParsedHandler (TextParsed);
1167 this.text.Append ("-->");
1180 void FlushText (bool ignoreEmptyString)
1182 string t = text.ToString ();
1185 if (ignoreEmptyString && t.Trim ().Length == 0)
1190 PageParserFilter pfilter = PageParserFilter;
1191 if (pfilter != null && !pfilter.ProcessCodeConstruct (CodeConstructType.ScriptTag, t))
1194 tparser.Scripts.Add (new ServerSideScript (t, new System.Web.Compilation.Location (tparser.Location)));
1198 if (tparser.DefaultDirectiveName == "application" && t.Trim () != "")
1199 throw new ParseException (location, "Content not valid for application file.");
1201 ControlBuilder current = stack.Builder;
1202 current.AppendLiteralString (t);
1203 if (current.NeedsTagInnerText ()) {
1204 tagInnerText.Append (t);
1209 bool BuilderHasOtherThan (Type type, ControlBuilder cb)
1211 ArrayList al = cb.OtherTags;
1212 if (al != null && al.Count > 0)
1219 foreach (object o in al) {
1223 tmp = o as ControlBuilder;
1225 string s = o as string;
1226 if (s != null && String.IsNullOrEmpty (s.Trim ()))
1232 if (tmp is System.Web.UI.WebControls.ContentBuilderInternal)
1235 if (tmp.ControlType != typeof (System.Web.UI.WebControls.Content))
1243 bool OtherControlsAllowed (ControlBuilder cb)
1248 if (!typeof (System.Web.UI.WebControls.Content).IsAssignableFrom (cb.ControlType))
1251 if (BuilderHasOtherThan (typeof (System.Web.UI.WebControls.Content), RootBuilder))
1258 public void AddControl (Type type, IDictionary attributes)
1260 ControlBuilder parent = stack.Builder;
1261 ControlBuilder builder = ControlBuilder.CreateBuilderFromType (tparser, parent, type, null, null,
1262 attributes, location.BeginLine,
1264 if (builder != null)
1265 parent.AppendSubBuilder (builder);
1268 bool ProcessTag (ILocation location, string tagid, TagAttributes atts, TagType tagtype, out bool ignored)
1271 if (isApplication) {
1272 if (String.Compare (tagid, "object", true, CultureInfo.InvariantCulture) != 0)
1273 throw new ParseException (location, "Invalid tag for application file.");
1276 ControlBuilder parent = stack.Builder;
1277 ControlBuilder builder = null;
1278 if (parent != null && parent.ControlType == typeof (HtmlTable) &&
1279 (String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0 ||
1280 String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0)) {
1285 Hashtable htable = (atts != null) ? atts.GetDictionary (null) : emptyHash;
1286 if (stack.Count > 1) {
1288 builder = parent.CreateSubBuilder (tagid, htable, null, tparser, location);
1289 } catch (TypeLoadException e) {
1290 throw new ParseException (Location, "Type not found.", e);
1291 } catch (Exception e) {
1292 throw new ParseException (Location, e.Message, e);
1296 bool runatServer = atts != null && atts.IsRunAtServer ();
1297 if (builder == null && runatServer) {
1298 string id = htable ["id"] as string;
1299 if (id != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (id))
1300 throw new ParseException (Location, "'" + id + "' is not a valid identifier");
1303 builder = RootBuilder.CreateSubBuilder (tagid, htable, null, tparser, location);
1304 } catch (TypeLoadException e) {
1305 throw new ParseException (Location, "Type not found.", e);
1306 } catch (HttpException e) {
1307 CompilationException inner = e.InnerException as CompilationException;
1311 throw new ParseException (Location, e.Message, e);
1312 } catch (Exception e) {
1313 throw new ParseException (Location, e.Message, e);
1317 if (builder == null)
1320 // This is as good as we can do for now - if the parsed location contains
1321 // both expressions and code render blocks then we're out of luck...
1322 string plainText = location.PlainText;
1323 if (!runatServer && plainText.IndexOf ("<%$") == -1&& plainText.IndexOf ("<%") > -1)
1326 PageParserFilter pfilter = PageParserFilter;
1327 if (pfilter != null && !pfilter.AllowControl (builder.ControlType, builder))
1328 throw new ParseException (Location, "Control type '" + builder.ControlType + "' not allowed.");
1330 if (!OtherControlsAllowed (builder))
1331 throw new ParseException (Location, "Only Content controls are allowed directly in a content page that contains Content controls.");
1334 builder.Location = location;
1335 builder.ID = htable ["id"] as string;
1336 if (typeof (HtmlForm).IsAssignableFrom (builder.ControlType)) {
1338 throw new ParseException (location, "Only one <form> allowed.");
1343 if (builder.HasBody () && !(builder is ObjectTagBuilder)) {
1344 if (builder is TemplateBuilder) {
1347 stack.Push (builder, location);
1349 if (!isApplication && builder is ObjectTagBuilder) {
1350 ObjectTagBuilder ot = (ObjectTagBuilder) builder;
1351 if (ot.Scope != null && ot.Scope.Length > 0)
1352 throw new ParseException (location, "Scope not allowed here");
1354 if (tagtype == TagType.Tag) {
1355 stack.Push (builder, location);
1360 parent.AppendSubBuilder (builder);
1361 builder.CloseControl ();
1367 string ReadFile (string filename)
1369 string realpath = tparser.MapPath (filename);
1370 using (StreamReader sr = new StreamReader (realpath, WebEncoding.FileEncoding)) {
1371 string content = sr.ReadToEnd ();
1376 bool ProcessScript (TagType tagtype, TagAttributes attributes)
1378 if (tagtype != TagType.Close) {
1379 if (attributes != null && attributes.IsRunAtServer ()) {
1380 string language = (string) attributes ["language"];
1381 if (language != null && language.Length > 0 && tparser.ImplicitLanguage)
1382 tparser.SetLanguage (language);
1383 CheckLanguage (language);
1384 string src = (string) attributes ["src"];
1386 if (src.Length == 0)
1387 throw new ParseException (Parser,
1388 "src cannot be an empty string");
1390 string content = ReadFile (src);
1392 TextParsed (Parser, content);
1395 if (tagtype != TagType.SelfClosing) {
1397 Parser.VerbatimID = "script";
1399 } else if (tagtype == TagType.Tag) {
1400 Parser.VerbatimID = "script";
1406 if (tagtype != TagType.SelfClosing) {
1407 Parser.VerbatimID = "script";
1410 TextParsed (location, location.PlainText);
1419 } else if (!ignore_text) {
1420 result = javascript;
1422 TextParsed (location, location.PlainText);
1424 ignore_text = false;
1431 bool CloseControl (string tagid)
1433 ControlBuilder current = stack.Builder;
1434 string btag = current.OriginalTagName;
1435 if (String.Compare (btag, "tbody", true, CultureInfo.InvariantCulture) != 0 &&
1436 String.Compare (tagid, "tbody", true, CultureInfo.InvariantCulture) == 0) {
1437 if (!current.ChildrenAsProperties) {
1439 TextParsed (location, location.PlainText);
1446 if (current.ControlType == typeof (HtmlTable) && String.Compare (tagid, "thead", true, CultureInfo.InvariantCulture) == 0)
1449 if (0 != String.Compare (tagid, btag, true, CultureInfo.InvariantCulture))
1452 // if (current is TemplateBuilder)
1453 // pop from the id list
1454 if (current.NeedsTagInnerText ()) {
1456 current.SetTagInnerText (tagInnerText.ToString ());
1457 } catch (Exception e) {
1458 throw new ParseException (current.Location, e.Message, e);
1461 tagInnerText.Length = 0;
1464 if (typeof (HtmlForm).IsAssignableFrom (current.ControlType)) {
1468 current.CloseControl ();
1470 stack.Builder.AppendSubBuilder (current);
1475 CodeConstructType MapTagTypeToConstructType (TagType tagtype)
1478 case TagType.CodeRenderExpression:
1479 return CodeConstructType.ExpressionSnippet;
1481 case TagType.CodeRender:
1482 return CodeConstructType.CodeSnippet;
1484 case TagType.DataBinding:
1485 return CodeConstructType.DataBindingSnippet;
1488 throw new InvalidOperationException ("Unexpected tag type.");
1493 bool ProcessCode (TagType tagtype, string code, ILocation location)
1496 PageParserFilter pfilter = PageParserFilter;
1499 // http://msdn.microsoft.com/en-us/library/system.web.ui.pageparserfilter.processcodeconstruct.aspx
1501 // The above page says if false is returned then we should NOT process the
1502 // code further, wheras in reality it's the other way around. The
1503 // ProcessCodeConstruct return value means whether or not the filter
1504 // _processed_ the code.
1506 if (pfilter != null && (!pfilter.AllowCode || pfilter.ProcessCodeConstruct (MapTagTypeToConstructType (tagtype), code)))
1509 ControlBuilder b = null;
1510 if (tagtype == TagType.CodeRender)
1511 b = new CodeRenderBuilder (code, false, location);
1512 else if (tagtype == TagType.CodeRenderExpression)
1513 b = new CodeRenderBuilder (code, true, location);
1514 else if (tagtype == TagType.DataBinding)
1515 b = new DataBindingBuilder (code, location);
1517 throw new HttpException ("Should never happen");
1519 stack.Builder.AppendSubBuilder (b);
1523 public ILocation Location {
1524 get { return location; }
1527 void CheckLanguage (string lang)
1529 if (lang == null || lang == "")
1532 if (String.Compare (lang, tparser.Language, true, CultureInfo.InvariantCulture) == 0)
1536 CompilationSection section = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
1537 if (section.Compilers[tparser.Language] != section.Compilers[lang])
1539 CompilationConfiguration cfg = CompilationConfiguration.GetInstance (HttpContext.Current);
1540 if (!cfg.Compilers.CompareLanguages (tparser.Language, lang))
1542 throw new ParseException (Location,
1543 String.Format ("Trying to mix language '{0}' and '{1}'.",
1544 tparser.Language, lang));
1547 // Used to get CodeRender tags in attribute values
1548 class CodeRenderParser
1551 ControlBuilder builder;
1552 AspGenerator generator;
1555 public CodeRenderParser (string str, ControlBuilder builder, ILocation location)
1558 this.builder = builder;
1559 this.location = location;
1562 public void AddChildren (AspGenerator generator)
1564 this.generator = generator;
1565 int index = str.IndexOf ("<%");
1567 DoParseExpressions (str);
1572 void DoParseExpressions (string str)
1574 int startIndex = 0, index = 0;
1575 Regex codeDirective = new Regex ("(<%(?!@)(?<code>(.|\\s)*?)%>)|(<[\\w:\\.]+.*?runat=[\"']?server[\"']?.*?/>)",
1576 RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant);
1578 int strLen = str.Length;
1580 while (index > -1 && startIndex < strLen) {
1581 match = codeDirective.Match (str, index);
1583 if (match.Success) {
1584 string value = match.Value;
1585 index = match.Index;
1586 if (index > startIndex)
1587 TextParsed (null, str.Substring (startIndex, index - startIndex));
1589 index += value.Length;
1595 index = str.IndexOf ('<', index);
1600 if (startIndex < strLen)
1601 TextParsed (null, str.Substring (startIndex));
1604 void DoParse (string str)
1606 AspParser parser = new AspParser ("@@code_render@@", new StringReader (str), location.BeginLine - 1, location as AspParser);
1607 parser.Error += new ParseErrorHandler (ParseError);
1608 parser.TagParsed += new TagParsedHandler (TagParsed);
1609 parser.TextParsed += new TextParsedHandler (TextParsed);
1613 void TagParsed (ILocation location, TagType tagtype, string tagid, TagAttributes attributes)
1616 case TagType.CodeRender:
1617 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, false, location));
1620 case TagType.CodeRenderExpression:
1621 builder.AppendSubBuilder (new CodeRenderBuilder (tagid, true, location));
1624 case TagType.DataBinding:
1625 builder.AppendSubBuilder (new DataBindingBuilder (tagid, location));
1629 case TagType.SelfClosing:
1631 if (generator != null)
1632 generator.TagParsed (location, tagtype, tagid, attributes);
1638 string text = location.PlainText;
1639 if (text != null && text.Trim ().Length > 0)
1640 builder.AppendLiteralString (text);
1645 void TextParsed (ILocation location, string text)
1647 builder.AppendLiteralString (text);
1650 void ParseError (ILocation location, string message)
1652 throw new ParseException (location, message);