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
;
37 using System
.Web
.UI
.HtmlControls
;
38 using System
.Web
.Util
;
40 namespace System
.Web
.Compilation
44 public ControlBuilder Builder
;
45 public ILocation Location
;
47 public BuilderLocation (ControlBuilder builder
, ILocation location
)
49 this.Builder
= builder
;
50 this.Location
= location
;
54 class BuilderLocationStack
: Stack
56 public override void Push (object o
)
58 if (!(o
is BuilderLocation
))
59 throw new InvalidOperationException ();
64 public virtual void Push (ControlBuilder builder
, ILocation location
)
66 BuilderLocation bl
= new BuilderLocation (builder
, location
);
70 public new BuilderLocation
Peek ()
72 return (BuilderLocation
) base.Peek ();
75 public new BuilderLocation
Pop ()
77 return (BuilderLocation
) base.Pop ();
80 public ControlBuilder Builder
{
81 get { return Peek ().Builder; }
93 files
= new Hashtable (); // may be this should be case sensitive for windows
94 parsers
= new Stack ();
97 public bool Push (AspParser parser
)
99 if (files
.Contains (parser
.Filename
))
102 files
[parser
.Filename
] = true;
103 parsers
.Push (parser
);
108 public AspParser
Pop ()
110 if (parsers
.Count
== 0)
113 files
.Remove (current
.Filename
);
114 AspParser result
= (AspParser
) parsers
.Pop ();
115 if (parsers
.Count
> 0)
116 current
= (AspParser
) parsers
.Peek ();
123 public AspParser Parser
{
124 get { return current; }
127 public string Filename
{
128 get { return current.Filename; }
141 public void Push (string tagid
)
151 return (string) tags
.Pop ();
154 public bool CompareTo (string tagid
)
159 return 0 == String
.Compare (tagid
, (string) tags
.Peek (), true);
163 get { return tags.Count; }
166 public string Current
{
167 get { return (string) tags.Peek (); }
174 BuilderLocationStack stack
;
175 TemplateParser tparser
;
177 RootBuilder rootBuilder
;
178 bool inScript
, javascript
;
181 StringBuilder tagInnerText
= new StringBuilder ();
182 static Hashtable emptyHash
= new Hashtable ();
186 public AspGenerator (TemplateParser tparser
)
188 this.tparser
= tparser
;
189 tparser
.AddDependency (tparser
.InputFile
);
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 throw new Exception ("Got type: " + type
);
221 void InitParser (string filename
)
223 StreamReader reader
= new StreamReader (filename
, WebEncoding
.FileEncoding
);
224 AspParser parser
= new AspParser (filename
, reader
);
226 parser
.Error
+= new ParseErrorHandler (ParseError
);
227 parser
.TagParsed
+= new TagParsedHandler (TagParsed
);
228 parser
.TextParsed
+= new TextParsedHandler (TextParsed
);
229 if (!pstack
.Push (parser
))
230 throw new ParseException (Location
, "Infinite recursion detected including file: " + filename
);
231 tparser
.AddDependency (filename
);
236 pstack
.Parser
.Parse ();
243 public Type
GetCompiledType ()
245 Type type
= (Type
) HttpRuntime
.Cache
.Get ("@@Type" + tparser
.InputFile
);
250 isApplication
= tparser
.DefaultDirectiveName
== "application";
251 InitParser (Path
.GetFullPath (tparser
.InputFile
));
255 PrintTree (rootBuilder
, 0);
259 throw new ParseException (stack
.Builder
.location
,
260 "Expecting </" + stack
.Builder
.TagName
+ ">" + stack
.Builder
);
262 BaseCompiler compiler
= GetCompilerFromType ();
264 type
= compiler
.GetCompiledType ();
265 CacheDependency cd
= new CacheDependency ((string[])
266 tparser
.Dependencies
.ToArray (typeof (string)));
268 HttpRuntime
.Cache
.Insert ("@@Type" + tparser
.InputFile
, type
, cd
);
273 static void PrintTree (ControlBuilder builder
, int indent
)
278 string i
= new string ('\t', indent
);
280 Console
.WriteLine ("b: {0} id: {1} type: {2} parent: {3}",
281 builder
, builder
.ID
, builder
.ControlType
, builder
.parentBuilder
);
283 if (builder
.Children
!= null)
284 foreach (object o
in builder
.Children
) {
285 if (o
is ControlBuilder
)
286 PrintTree ((ControlBuilder
) o
, indent
++);
291 static void PrintLocation (ILocation loc
)
293 Console
.WriteLine ("\tFile name: " + loc
.Filename
);
294 Console
.WriteLine ("\tBegin line: " + loc
.BeginLine
);
295 Console
.WriteLine ("\tEnd line: " + loc
.EndLine
);
296 Console
.WriteLine ("\tBegin column: " + loc
.BeginColumn
);
297 Console
.WriteLine ("\tEnd column: " + loc
.EndColumn
);
298 Console
.WriteLine ("\tPlainText: " + loc
.PlainText
);
299 Console
.WriteLine ();
302 void ParseError (ILocation location
, string message
)
304 throw new ParseException (location
, message
);
307 void TagParsed (ILocation location
, TagType tagtype
, string tagid
, TagAttributes attributes
)
309 this.location
= new Location (location
);
311 tparser
.Location
= location
;
313 if (text
.Length
!= 0)
316 if (0 == String
.Compare (tagid
, "script", true)) {
317 if (ProcessScript (tagtype
, attributes
))
322 case TagType
.Directive
:
324 tagid
= tparser
.DefaultDirectiveName
;
326 tparser
.AddDirective (tagid
, attributes
.GetDictionary (null));
329 if (ProcessTag (tagid
, attributes
, tagtype
))
333 stack
.Builder
.EnsureOtherTags ();
334 stack
.Builder
.OtherTags
.Add (tagid
);
337 TextParsed (location
, location
.PlainText
);
340 bool notServer
= (inForm
&& TryRemoveTag (tagid
, stack
.Builder
.OtherTags
));
341 if (!notServer
&& CloseControl (tagid
))
344 TextParsed (location
, location
.PlainText
);
346 case TagType
.SelfClosing
:
347 int count
= stack
.Count
;
348 if (!ProcessTag (tagid
, attributes
, tagtype
)) {
349 TextParsed (location
, location
.PlainText
);
350 } else if (stack
.Count
!= count
) {
351 CloseControl (tagid
);
354 case TagType
.DataBinding
:
355 goto case TagType
.CodeRender
;
356 case TagType
.CodeRenderExpression
:
357 goto case TagType
.CodeRender
;
358 case TagType
.CodeRender
:
360 throw new ParseException (location
, "Invalid content for application file.");
362 ProcessCode (tagtype
, tagid
, location
);
364 case TagType
.Include
:
366 throw new ParseException (location
, "Invalid content for application file.");
368 string file
= attributes
["virtual"] as string;
369 bool isvirtual
= (file
!= null);
371 file
= attributes
["file"] as string;
374 file
= tparser
.MapPath (file
);
376 file
= GetIncludeFilePath (tparser
.BaseDir
, file
);
385 //PrintLocation (location);
388 static bool TryRemoveTag (string tagid
, ArrayList otags
)
390 if (otags
== null || otags
.Count
== 0)
393 int idx
= otags
.Count
- 1;
394 string otagid
= (string) otags
[idx
];
395 if (0 != String
.Compare (tagid
, otagid
, true))
398 otags
.RemoveAt (idx
);
402 static string GetIncludeFilePath (string basedir
, string filename
)
404 if (Path
.DirectorySeparatorChar
== '/')
405 filename
= filename
.Replace ("\\", "/");
407 return Path
.GetFullPath (Path
.Combine (basedir
, filename
));
410 void TextParsed (ILocation location
, string text
)
412 if (text
.IndexOf ("<%") != -1 && !inScript
) {
413 if (this.text
.Length
> 0)
415 CodeRenderParser r
= new CodeRenderParser (text
, stack
.Builder
);
420 this.text
.Append (text
);
421 //PrintLocation (location);
426 string t
= text
.ToString ();
429 // TODO: store location
430 tparser
.Scripts
.Add (t
);
434 if (tparser
.DefaultDirectiveName
== "application" && t
.Trim () != "")
435 throw new ParseException (location
, "Content not valid for application file.");
437 ControlBuilder current
= stack
.Builder
;
438 current
.AppendLiteralString (t
);
439 if (current
.NeedsTagInnerText ()) {
440 tagInnerText
.Append (t
);
444 bool ProcessTag (string tagid
, TagAttributes atts
, TagType tagtype
)
446 if ((atts
== null || !atts
.IsRunAtServer ()) && String
.Compare (tagid
, "tbody", true) == 0) {
447 // MS completely ignores tbody or, if runat="server", fails when compiling
449 return stack
.Builder
.ChildrenAsProperties
;
455 if (String
.Compare (tagid
, "object", true) != 0)
456 throw new ParseException (location
, "Invalid tag for application file.");
459 ControlBuilder parent
= stack
.Builder
;
460 ControlBuilder builder
= null;
461 Hashtable htable
= (atts
!= null) ? atts
.GetDictionary (null) : emptyHash
;
462 if (stack
.Count
> 1) {
464 builder
= parent
.CreateSubBuilder (tagid
, htable
, null, tparser
, location
);
465 } catch (TypeLoadException e
) {
466 throw new ParseException (Location
, "Type not found.", e
);
467 } catch (Exception e
) {
468 throw new ParseException (Location
, e
.Message
, e
);
472 if (builder
== null && atts
!= null && atts
.IsRunAtServer ()) {
473 string id
= htable
["id"] as string;
474 if (id
!= null && !CodeGenerator
.IsValidLanguageIndependentIdentifier (id
))
475 throw new ParseException (Location
, "'" + id
+ "' is not a valid identifier");
478 builder
= rootBuilder
.CreateSubBuilder (tagid
, htable
, null, tparser
, location
);
479 } catch (TypeLoadException e
) {
480 throw new ParseException (Location
, "Type not found.", e
);
481 } catch (Exception e
) {
482 throw new ParseException (Location
, e
.Message
, e
);
489 builder
.location
= location
;
490 builder
.ID
= htable
["id"] as string;
491 if (typeof (HtmlForm
).IsAssignableFrom (builder
.ControlType
)) {
493 throw new ParseException (location
, "Only one <form> allowed.");
496 formTags
= new TagStack ();
499 if (builder
.HasBody () && !(builder
is ObjectTagBuilder
)) {
500 if (builder
is TemplateBuilder
) {
503 stack
.Push (builder
, location
);
505 if (!isApplication
&& builder
is ObjectTagBuilder
) {
506 ObjectTagBuilder ot
= (ObjectTagBuilder
) builder
;
507 if (ot
.Scope
!= null && ot
.Scope
!= "")
508 throw new ParseException (location
, "Scope not allowed here");
510 if (tagtype
== TagType
.Tag
) {
511 stack
.Push (builder
, location
);
516 parent
.AppendSubBuilder (builder
);
517 builder
.CloseControl ();
523 bool ProcessScript (TagType tagtype
, TagAttributes attributes
)
525 if (tagtype
!= TagType
.Close
) {
526 if (attributes
!= null && attributes
.IsRunAtServer ()) {
527 CheckLanguage ((string) attributes
["language"]);
528 if (tagtype
== TagType
.Tag
) {
529 Parser
.VerbatimID
= "script";
531 } //else if (tagtype == TagType.SelfClosing)
532 // load script file here
536 if (tagtype
!= TagType
.SelfClosing
) {
537 Parser
.VerbatimID
= "script";
540 TextParsed (location
, location
.PlainText
);
552 TextParsed (location
, location
.PlainText
);
558 bool CloseControl (string tagid
)
560 ControlBuilder current
= stack
.Builder
;
561 if (String
.Compare (tagid
, "tbody", true) == 0) {
562 if (!current
.ChildrenAsProperties
) {
564 TextParsed (location
, location
.PlainText
);
571 string btag
= current
.TagName
;
572 if (0 != String
.Compare (tagid
, btag
, true))
575 // if (current is TemplateBuilder)
576 // pop from the id list
577 if (current
.NeedsTagInnerText ()) {
579 current
.SetTagInnerText (tagInnerText
.ToString ());
580 } catch (Exception e
) {
581 throw new ParseException (current
.location
, e
.Message
, e
);
584 tagInnerText
.Length
= 0;
587 if (typeof (HtmlForm
).IsAssignableFrom (current
.ControlType
)) {
591 current
.CloseControl ();
593 stack
.Builder
.AppendSubBuilder (current
);
597 bool ProcessCode (TagType tagtype
, string code
, ILocation location
)
599 ControlBuilder b
= null;
600 if (tagtype
== TagType
.CodeRender
)
601 b
= new CodeRenderBuilder (code
, false, location
);
602 else if (tagtype
== TagType
.CodeRenderExpression
)
603 b
= new CodeRenderBuilder (code
, true, location
);
604 else if (tagtype
== TagType
.DataBinding
)
605 b
= new DataBindingBuilder (code
, location
);
607 throw new HttpException ("Should never happen");
609 stack
.Builder
.AppendSubBuilder (b
);
613 public ILocation Location
{
614 get { return location; }
617 void CheckLanguage (string lang
)
619 if (lang
== null || lang
== "")
622 if (String
.Compare (lang
, tparser
.Language
, true) != 0) {
623 throw new ParseException (Location
,
624 String
.Format ("Trying to mix language '{0}' and '{1}'.",
625 tparser
.Language
, lang
));
629 // Used to get CodeRender tags in attribute values
630 class CodeRenderParser
633 ControlBuilder builder
;
635 public CodeRenderParser (string str
, ControlBuilder builder
)
638 this.builder
= builder
;
641 public void AddChildren ()
643 int index
= str
.IndexOf ("<%");
645 TextParsed (null, str
.Substring (0, index
));
646 str
= str
.Substring (index
);
649 AspParser parser
= new AspParser ("@@inner_string@@", new StringReader (str
));
650 parser
.Error
+= new ParseErrorHandler (ParseError
);
651 parser
.TagParsed
+= new TagParsedHandler (TagParsed
);
652 parser
.TextParsed
+= new TextParsedHandler (TextParsed
);
656 void TagParsed (ILocation location
, TagType tagtype
, string tagid
, TagAttributes attributes
)
658 if (tagtype
== TagType
.CodeRender
)
659 builder
.AppendSubBuilder (new CodeRenderBuilder (tagid
, false, location
));
660 else if (tagtype
== TagType
.CodeRenderExpression
)
661 builder
.AppendSubBuilder (new CodeRenderBuilder (tagid
, true, location
));
662 else if (tagtype
== TagType
.DataBinding
)
663 builder
.AppendSubBuilder (new DataBindingBuilder (tagid
, location
));
665 builder
.AppendLiteralString (location
.PlainText
);
668 void TextParsed (ILocation location
, string text
)
670 builder
.AppendLiteralString (text
);
673 void ParseError (ILocation location
, string message
)
675 throw new ParseException (location
, message
);