2 // System.Web.Compilation.AspParser
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Marek Habersack <mhabersack@novell.com>
8 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
9 // (C) 2004-2009 Novell, Inc (http://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
.ComponentModel
;
34 using System
.Collections
;
35 using System
.Globalization
;
38 using System
.Web
.Util
;
39 using System
.Security
.Cryptography
;
41 namespace System
.Web
.Compilation
43 delegate void ParseErrorHandler (ILocation location
, string message
);
44 delegate void TextParsedHandler (ILocation location
, string text
);
45 delegate void TagParsedHandler (ILocation location
, TagType tagtype
, string id
, TagAttributes attributes
);
46 delegate void ParsingCompleteHandler ();
48 class AspParser
: ILocation
50 static readonly object errorEvent
= new object ();
51 static readonly object tagParsedEvent
= new object ();
52 static readonly object textParsedEvent
= new object ();
53 static readonly object parsingCompleteEvent
= new object();
56 AspTokenizer tokenizer
;
57 int beginLine
, endLine
;
58 int beginColumn
, endColumn
;
59 int beginPosition
, endPosition
;
63 StringReader fileReader
;
65 int _internalLineOffset
;
66 int _internalPositionOffset
;
69 EventHandlerList events
= new EventHandlerList ();
71 public event ParseErrorHandler Error
{
72 add { events.AddHandler (errorEvent, value); }
73 remove { events.RemoveHandler (errorEvent, value); }
76 public event TagParsedHandler TagParsed
{
77 add { events.AddHandler (tagParsedEvent, value); }
78 remove { events.RemoveHandler (tagParsedEvent, value); }
81 public event TextParsedHandler TextParsed
{
82 add { events.AddHandler (textParsedEvent, value); }
83 remove { events.RemoveHandler (textParsedEvent, value); }
86 public event ParsingCompleteHandler ParsingComplete
{
87 add { events.AddHandler (parsingCompleteEvent, value); }
88 remove { events.RemoveHandler (parsingCompleteEvent, value); }
91 public AspParser (string filename
, TextReader input
)
93 this.filename
= filename
;
94 this.fileText
= input
.ReadToEnd ();
95 this.fileReader
= new StringReader (this.fileText
);
96 this._internalLineOffset
= 0;
97 tokenizer
= new AspTokenizer (this.fileReader
);
100 public AspParser (string filename
, TextReader input
, int startLineOffset
, int positionOffset
, AspParser outer
)
101 : this (filename
, input
)
103 this._internal
= true;
104 this._internalLineOffset
= startLineOffset
;
105 this._internalPositionOffset
= positionOffset
;
109 public byte[] MD5Checksum
{
111 if (checksum
== null)
114 return checksum
.Hash
;
118 public int BeginPosition
{
119 get { return beginPosition; }
122 public int EndPosition
{
123 get { return endPosition; }
126 public int BeginLine
{
129 return beginLine
+ _internalLineOffset
;
135 public int BeginColumn
{
136 get { return beginColumn; }
142 return endLine
+ _internalLineOffset
;
147 public int EndColumn
{
148 get { return endColumn; }
151 public string FileText
{
155 if (_internal
&& outer
!= null)
156 ret
= outer
.FileText
;
158 if (ret
== null && fileText
!= null)
165 public string PlainText
{
167 if (beginPosition
>= endPosition
|| fileText
== null)
170 string text
= FileText
;
173 if (_internal
&& outer
!= null) {
174 start
= beginPosition
+ _internalPositionOffset
;
175 len
= (endPosition
+ _internalPositionOffset
) - start
;
177 start
= beginPosition
;
178 len
= endPosition
- beginPosition
;
182 return text
.Substring (start
, len
);
188 public string Filename
{
190 if (_internal
&& outer
!= null)
191 return outer
.Filename
;
197 public string VerbatimID
{
199 tokenizer
.Verbatim
= true;
204 bool Eat (int expected_token
)
206 int token
= tokenizer
.get_token ();
207 if (token
!= expected_token
) {
208 tokenizer
.put_back ();
212 endLine
= tokenizer
.EndLine
;
213 endColumn
= tokenizer
.EndColumn
;
219 beginLine
= tokenizer
.BeginLine
;
220 beginColumn
= tokenizer
.BeginColumn
;
221 beginPosition
= tokenizer
.Position
- 1;
226 endLine
= tokenizer
.EndLine
;
227 endColumn
= tokenizer
.EndColumn
;
228 endPosition
= tokenizer
.Position
;
233 if (tokenizer
== null) {
234 OnError ("AspParser not initialized properly.");
240 TagAttributes attributes
;
241 TagType tagtype
= TagType
.Text
;
242 StringBuilder text
= new StringBuilder ();
245 while ((token
= tokenizer
.get_token ()) != Token
.EOF
) {
248 if (tokenizer
.Verbatim
){
249 string end_verbatim
= "</" + verbatimID
+ ">";
250 string verbatim_text
= GetVerbatim (token
, end_verbatim
);
252 if (verbatim_text
== null)
253 OnError ("Unexpected EOF processing " + verbatimID
);
255 tokenizer
.Verbatim
= false;
258 endPosition
-= end_verbatim
.Length
;
259 OnTextParsed (verbatim_text
);
260 beginPosition
= endPosition
;
261 endPosition
+= end_verbatim
.Length
;
262 OnTagParsed (TagType
.Close
, verbatimID
, null);
267 GetTag (out tagtype
, out id
, out attributes
);
269 if (tagtype
== TagType
.ServerComment
)
272 if (tagtype
== TagType
.Text
)
275 OnTagParsed (tagtype
, id
, attributes
);
280 if (tokenizer
.Value
.Trim ().Length
== 0 && tagtype
== TagType
.Directive
) {
286 text
.Append (tokenizer
.Value
);
287 token
= tokenizer
.get_token ();
288 } while (token
!= '<' && token
!= Token
.EOF
);
290 tokenizer
.put_back ();
292 OnTextParsed (text
.ToString ());
295 if (fileReader
!= null) {
299 checksum
= tokenizer
.Checksum
;
303 OnParsingComplete ();
306 bool GetInclude (string str
, out string pathType
, out string filename
)
310 str
= str
.Substring (2).Trim ();
311 int len
= str
.Length
;
312 int lastQuote
= str
.LastIndexOf ('"');
313 if (len
< 10 || lastQuote
!= len
- 1)
316 if (!StrUtils
.StartsWith (str
, "#include ", true))
319 str
= str
.Substring (9).Trim ();
320 bool isfile
= (StrUtils
.StartsWith (str
,"file", true));
321 if (!isfile
&& !StrUtils
.StartsWith (str
, "virtual", true))
324 pathType
= (isfile
) ? "file" : "virtual";
325 if (str
.Length
< pathType
.Length
+ 3)
328 str
= str
.Substring (pathType
.Length
).Trim ();
329 if (str
.Length
< 3 || str
[0] != '=')
333 for (; index
< str
.Length
; index
++) {
334 if (Char
.IsWhiteSpace (str
[index
]))
336 else if (str
[index
] == '"')
340 if (index
== str
.Length
|| index
== lastQuote
)
343 str
= str
.Substring (index
);
344 if (str
.Length
== 2) { // only quotes
345 OnError ("Empty file name.");
349 filename
= str
.Trim ().Substring (index
, str
.Length
- 2);
350 if (filename
.LastIndexOf ('"') != -1)
351 return false; // file=""" -> no error
356 void GetTag (out TagType tagtype
, out string id
, out TagAttributes attributes
)
358 int token
= tokenizer
.get_token ();
360 tagtype
= TagType
.ServerComment
;
365 GetServerTag (out tagtype
, out id
, out attributes
);
368 if (!Eat (Token
.IDENTIFIER
))
369 OnError ("expecting TAGNAME");
371 id
= tokenizer
.Value
;
373 OnError ("expecting '>'. Got '" + id
+ "'");
375 tagtype
= TagType
.Close
;
378 bool double_dash
= Eat (Token
.DOUBLEDASH
);
380 tokenizer
.put_back ();
382 tokenizer
.Verbatim
= true;
383 string end
= double_dash
? "-->" : ">";
384 string comment
= GetVerbatim (tokenizer
.get_token (), end
);
385 tokenizer
.Verbatim
= false;
387 OnError ("Unfinished HTML comment/DTD");
389 string pathType
, filename
;
390 if (double_dash
&& GetInclude (comment
, out pathType
, out filename
)) {
391 tagtype
= TagType
.Include
;
392 attributes
= new TagAttributes ();
393 attributes
.Add (pathType
, filename
);
395 tagtype
= TagType
.Text
;
396 id
= "<!" + comment
+ end
;
399 case Token
.IDENTIFIER
:
400 if (this.filename
== "@@inner_string@@") {
401 // Actually not tag but "xxx < yyy" stuff in inner_string!
402 tagtype
= TagType
.Text
;
403 tokenizer
.InTag
= false;
404 id
= "<" + tokenizer
.Odds
+ tokenizer
.Value
;
406 id
= tokenizer
.Value
;
408 attributes
= GetAttributes ();
409 } catch (Exception e
) {
414 tagtype
= TagType
.Tag
;
415 if (Eat ('/') && Eat ('>')) {
416 tagtype
= TagType
.SelfClosing
;
417 } else if (!Eat ('>')) {
418 if (attributes
.IsRunAtServer ()) {
419 OnError ("The server tag is not well formed.");
422 tokenizer
.Verbatim
= true;
423 attributes
.Add (String
.Empty
, GetVerbatim (tokenizer
.get_token (), ">") + ">");
424 tokenizer
.Verbatim
= false;
430 string idvalue
= null;
431 // This is to handle code like:
433 // <asp:ListItem runat="server"> < </asp:ListItem>
435 if ((char)token
== '<') {
436 string odds
= tokenizer
.Odds
;
437 if (odds
!= null && odds
.Length
> 0 && Char
.IsWhiteSpace (odds
[0])) {
438 tokenizer
.put_back ();
441 idvalue
= tokenizer
.Value
;
443 idvalue
= tokenizer
.Value
;
445 tagtype
= TagType
.Text
;
446 tokenizer
.InTag
= false;
452 TagAttributes
GetAttributes ()
455 TagAttributes attributes
;
457 bool wellFormedForServer
= true;
459 attributes
= new TagAttributes ();
460 while ((token
= tokenizer
.get_token ()) != Token
.EOF
){
461 if (token
== '<' && Eat ('%')) {
462 tokenizer
.Verbatim
= true;
463 attributes
.Add (String
.Empty
, "<%" +
464 GetVerbatim (tokenizer
.get_token (), "%>") + "%>");
465 tokenizer
.Verbatim
= false;
466 tokenizer
.InTag
= true;
470 if (token
!= Token
.IDENTIFIER
)
473 id
= tokenizer
.Value
;
475 if (Eat (Token
.ATTVALUE
)){
476 attributes
.Add (id
, tokenizer
.Value
);
477 wellFormedForServer
&= tokenizer
.AlternatingQuotes
;
478 } else if (Eat ('<') && Eat ('%')) {
479 tokenizer
.Verbatim
= true;
480 attributes
.Add (id
, "<%" +
481 GetVerbatim (tokenizer
.get_token (), "%>") + "%>");
482 tokenizer
.Verbatim
= false;
483 tokenizer
.InTag
= true;
485 OnError ("expected ATTVALUE");
489 attributes
.Add (id
, null);
493 tokenizer
.put_back ();
495 if (attributes
.IsRunAtServer () && !wellFormedForServer
) {
496 OnError ("The server tag is not well formed.");
503 string GetVerbatim (int token
, string end
)
505 StringBuilder vb_text
= new StringBuilder ();
506 StringBuilder tmp
= new StringBuilder ();
509 if (tokenizer
.Value
.Length
> 1){
510 // May be we have a put_back token that is not a single character
511 vb_text
.Append (tokenizer
.Value
);
512 token
= tokenizer
.get_token ();
515 end
= end
.ToLower (Helpers
.InvariantCulture
);
517 for (int k
= 0; k
< end
.Length
; k
++)
518 if (end
[0] == end
[k
])
521 while (token
!= Token
.EOF
){
522 if (Char
.ToLower ((char) token
, Helpers
.InvariantCulture
) == end
[i
]){
523 if (++i
>= end
.Length
)
525 tmp
.Append ((char) token
);
526 token
= tokenizer
.get_token ();
529 if (repeated
> 1 && i
== repeated
&& (char) token
== end
[0]) {
530 vb_text
.Append ((char) token
);
531 token
= tokenizer
.get_token ();
534 vb_text
.Append (tmp
.ToString ());
535 tmp
.Remove (0, tmp
.Length
);
539 vb_text
.Append ((char) token
);
540 token
= tokenizer
.get_token ();
543 if (token
== Token
.EOF
)
544 OnError ("Expecting " + end
+ " and got EOF.");
546 return RemoveComments (vb_text
.ToString ());
549 string RemoveComments (string text
)
552 int start
= text
.IndexOf ("<%--");
554 while (start
!= -1) {
555 end
= text
.IndexOf ("--%>");
556 if (end
== -1 || end
<= start
+ 1)
559 text
= text
.Remove (start
, end
- start
+ 4);
560 start
= text
.IndexOf ("<%--");
566 void GetServerTag (out TagType tagtype
, out string id
, out TagAttributes attributes
)
569 bool old
= tokenizer
.ExpectAttrValue
;
571 tokenizer
.ExpectAttrValue
= false;
573 tokenizer
.ExpectAttrValue
= old
;
574 tagtype
= TagType
.Directive
;
576 if (Eat (Token
.DIRECTIVE
))
577 id
= tokenizer
.Value
;
579 attributes
= GetAttributes ();
580 if (!Eat ('%') || !Eat ('>'))
581 OnError ("expecting '%>'");
586 if (Eat (Token
.DOUBLEDASH
)) {
587 tokenizer
.ExpectAttrValue
= old
;
588 tokenizer
.Verbatim
= true;
589 inside_tags
= GetVerbatim (tokenizer
.get_token (), "--%>");
590 tokenizer
.Verbatim
= false;
593 tagtype
= TagType
.ServerComment
;
597 tokenizer
.ExpectAttrValue
= old
;
601 bool codeRenderEncode
;
604 databinding
= !varname
&& Eat ('#');
606 codeRenderEncode
= !databinding
&& !varname
&& Eat (':');
608 string odds
= tokenizer
.Odds
;
610 tokenizer
.Verbatim
= true;
611 inside_tags
= GetVerbatim (tokenizer
.get_token (), "%>");
612 if (databinding
&& odds
!= null && odds
.Length
> 0) {
615 // We encountered <% #something here %>, this should be passed
616 // verbatim to the compiler
617 inside_tags
= '#' + inside_tags
;
620 tokenizer
.Verbatim
= false;
624 tagtype
= TagType
.DataBinding
;
626 tagtype
= TagType
.CodeRenderExpression
;
628 else if (codeRenderEncode
)
629 tagtype
= TagType
.CodeRenderEncode
;
632 tagtype
= TagType
.CodeRender
;
635 public override string ToString ()
637 StringBuilder sb
= new StringBuilder ("AspParser {");
638 if (filename
!= null && filename
.Length
> 0)
639 sb
.AppendFormat ("{0}:{1}.{2}", filename
, beginLine
, beginColumn
);
642 return sb
.ToString ();
645 void OnError (string msg
)
647 ParseErrorHandler eh
= events
[errorEvent
] as ParseErrorHandler
;
652 void OnTagParsed (TagType tagtype
, string id
, TagAttributes attributes
)
654 TagParsedHandler eh
= events
[tagParsedEvent
] as TagParsedHandler
;
656 eh (this, tagtype
, id
, attributes
);
659 void OnTextParsed (string text
)
661 TextParsedHandler eh
= events
[textParsedEvent
] as TextParsedHandler
;
666 void OnParsingComplete ()
668 ParsingCompleteHandler eh
= events
[parsingCompleteEvent
] as ParsingCompleteHandler
;