2010-06-21 Atsushi Enomoto <atsushi@ximian.com>
[mcs.git] / class / System.Web / System.Web.Compilation / AspParser.cs
blob3a9d22e089ea683659d1bb43fb68125dc4fabf73
1 //
2 // System.Web.Compilation.AspParser
3 //
4 // Authors:
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Marek Habersack <mhabersack@novell.com>
7 //
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:
20 //
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 //
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.
32 using System;
33 using System.ComponentModel;
34 using System.Collections;
35 using System.Globalization;
36 using System.IO;
37 using System.Text;
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();
55 MD5 checksum;
56 AspTokenizer tokenizer;
57 int beginLine, endLine;
58 int beginColumn, endColumn;
59 int beginPosition, endPosition;
60 string filename;
61 string verbatimID;
62 string fileText;
63 StringReader fileReader;
64 bool _internal;
65 int _internalLineOffset;
66 int _internalPositionOffset;
67 AspParser outer;
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;
106 this.outer = outer;
109 public byte[] MD5Checksum {
110 get {
111 if (checksum == null)
112 return new byte[0];
114 return checksum.Hash;
118 public int BeginPosition {
119 get { return beginPosition; }
122 public int EndPosition {
123 get { return endPosition; }
126 public int BeginLine {
127 get {
128 if (_internal)
129 return beginLine + _internalLineOffset;
131 return beginLine;
135 public int BeginColumn {
136 get { return beginColumn; }
139 public int EndLine {
140 get {
141 if (_internal)
142 return endLine + _internalLineOffset;
143 return endLine;
147 public int EndColumn {
148 get { return endColumn; }
151 public string FileText {
152 get {
153 string ret = null;
155 if (_internal && outer != null)
156 ret = outer.FileText;
158 if (ret == null && fileText != null)
159 ret = fileText;
161 return ret;
165 public string PlainText {
166 get {
167 if (beginPosition >= endPosition || fileText == null)
168 return null;
170 string text = FileText;
171 int start, len;
173 if (_internal && outer != null) {
174 start = beginPosition + _internalPositionOffset;
175 len = (endPosition + _internalPositionOffset) - start;
176 } else {
177 start = beginPosition;
178 len = endPosition - beginPosition;
181 if (text != null)
182 return text.Substring (start, len);
184 return null;
188 public string Filename {
189 get {
190 if (_internal && outer != null)
191 return outer.Filename;
193 return filename;
197 public string VerbatimID {
198 set {
199 tokenizer.Verbatim = true;
200 verbatimID = value;
204 bool Eat (int expected_token)
206 int token = tokenizer.get_token ();
207 if (token != expected_token) {
208 tokenizer.put_back ();
209 return false;
212 endLine = tokenizer.EndLine;
213 endColumn = tokenizer.EndColumn;
214 return true;
217 void BeginElement ()
219 beginLine = tokenizer.BeginLine;
220 beginColumn = tokenizer.BeginColumn;
221 beginPosition = tokenizer.Position - 1;
224 void EndElement ()
226 endLine = tokenizer.EndLine;
227 endColumn = tokenizer.EndColumn;
228 endPosition = tokenizer.Position;
231 public void Parse ()
233 if (tokenizer == null) {
234 OnError ("AspParser not initialized properly.");
235 return;
238 int token;
239 string id;
240 TagAttributes attributes;
241 TagType tagtype = TagType.Text;
242 StringBuilder text = new StringBuilder ();
244 try {
245 while ((token = tokenizer.get_token ()) != Token.EOF) {
246 BeginElement ();
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;
257 EndElement ();
258 endPosition -= end_verbatim.Length;
259 OnTextParsed (verbatim_text);
260 beginPosition = endPosition;
261 endPosition += end_verbatim.Length;
262 OnTagParsed (TagType.Close, verbatimID, null);
263 continue;
266 if (token == '<') {
267 GetTag (out tagtype, out id, out attributes);
268 EndElement ();
269 if (tagtype == TagType.ServerComment)
270 continue;
272 if (tagtype == TagType.Text)
273 OnTextParsed (id);
274 else
275 OnTagParsed (tagtype, id, attributes);
277 continue;
280 if (tokenizer.Value.Trim ().Length == 0 && tagtype == TagType.Directive) {
281 continue;
284 text.Length = 0;
285 do {
286 text.Append (tokenizer.Value);
287 token = tokenizer.get_token ();
288 } while (token != '<' && token != Token.EOF);
290 tokenizer.put_back ();
291 EndElement ();
292 OnTextParsed (text.ToString ());
294 } finally {
295 if (fileReader != null) {
296 fileReader.Close ();
297 fileReader = null;
299 checksum = tokenizer.Checksum;
300 tokenizer = null;
303 OnParsingComplete ();
306 bool GetInclude (string str, out string pathType, out string filename)
308 pathType = null;
309 filename = null;
310 str = str.Substring (2).Trim ();
311 int len = str.Length;
312 int lastQuote = str.LastIndexOf ('"');
313 if (len < 10 || lastQuote != len - 1)
314 return false;
316 if (!StrUtils.StartsWith (str, "#include ", true))
317 return false;
319 str = str.Substring (9).Trim ();
320 bool isfile = (StrUtils.StartsWith (str ,"file", true));
321 if (!isfile && !StrUtils.StartsWith (str, "virtual", true))
322 return false;
324 pathType = (isfile) ? "file" : "virtual";
325 if (str.Length < pathType.Length + 3)
326 return false;
328 str = str.Substring (pathType.Length).Trim ();
329 if (str.Length < 3 || str [0] != '=')
330 return false;
332 int index = 1;
333 for (; index < str.Length; index++) {
334 if (Char.IsWhiteSpace (str [index]))
335 continue;
336 else if (str [index] == '"')
337 break;
340 if (index == str.Length || index == lastQuote)
341 return false;
343 str = str.Substring (index);
344 if (str.Length == 2) { // only quotes
345 OnError ("Empty file name.");
346 return false;
349 filename = str.Trim ().Substring (index, str.Length - 2);
350 if (filename.LastIndexOf ('"') != -1)
351 return false; // file=""" -> no error
353 return true;
356 void GetTag (out TagType tagtype, out string id, out TagAttributes attributes)
358 int token = tokenizer.get_token ();
360 tagtype = TagType.ServerComment;
361 id = null;
362 attributes = null;
363 switch (token){
364 case '%':
365 GetServerTag (out tagtype, out id, out attributes);
366 break;
367 case '/':
368 if (!Eat (Token.IDENTIFIER))
369 OnError ("expecting TAGNAME");
371 id = tokenizer.Value;
372 if (!Eat ('>'))
373 OnError ("expecting '>'. Got '" + id + "'");
375 tagtype = TagType.Close;
376 break;
377 case '!':
378 bool double_dash = Eat (Token.DOUBLEDASH);
379 if (double_dash)
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;
386 if (comment == null)
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);
394 } else {
395 tagtype = TagType.Text;
396 id = "<!" + comment + end;
398 break;
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;
405 } else {
406 id = tokenizer.Value;
407 try {
408 attributes = GetAttributes ();
409 } catch (Exception e) {
410 OnError (e.Message);
411 break;
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.");
420 break;
422 tokenizer.Verbatim = true;
423 attributes.Add (String.Empty, GetVerbatim (tokenizer.get_token (), ">") + ">");
424 tokenizer.Verbatim = false;
428 break;
429 default:
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 ();
439 idvalue = odds;
440 } else
441 idvalue = tokenizer.Value;
442 } else
443 idvalue = tokenizer.Value;
445 tagtype = TagType.Text;
446 tokenizer.InTag = false;
447 id = "<" + idvalue;
448 break;
452 TagAttributes GetAttributes ()
454 int token;
455 TagAttributes attributes;
456 string id;
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;
467 continue;
470 if (token != Token.IDENTIFIER)
471 break;
473 id = tokenizer.Value;
474 if (Eat ('=')){
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;
484 } else {
485 OnError ("expected ATTVALUE");
486 return null;
488 } else {
489 attributes.Add (id, null);
493 tokenizer.put_back ();
495 if (attributes.IsRunAtServer () && !wellFormedForServer) {
496 OnError ("The server tag is not well formed.");
497 return null;
500 return attributes;
503 string GetVerbatim (int token, string end)
505 StringBuilder vb_text = new StringBuilder ();
506 StringBuilder tmp = new StringBuilder ();
507 int i = 0;
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);
516 int repeated = 0;
517 for (int k = 0; k < end.Length; k++)
518 if (end [0] == end [k])
519 repeated++;
521 while (token != Token.EOF){
522 if (Char.ToLower ((char) token, Helpers.InvariantCulture) == end [i]){
523 if (++i >= end.Length)
524 break;
525 tmp.Append ((char) token);
526 token = tokenizer.get_token ();
527 continue;
528 } else if (i > 0) {
529 if (repeated > 1 && i == repeated && (char) token == end [0]) {
530 vb_text.Append ((char) token);
531 token = tokenizer.get_token ();
532 continue;
534 vb_text.Append (tmp.ToString ());
535 tmp.Remove (0, tmp.Length);
536 i = 0;
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)
551 int end;
552 int start = text.IndexOf ("<%--");
554 while (start != -1) {
555 end = text.IndexOf ("--%>");
556 if (end == -1 || end <= start + 1)
557 break;
559 text = text.Remove (start, end - start + 4);
560 start = text.IndexOf ("<%--");
563 return text;
566 void GetServerTag (out TagType tagtype, out string id, out TagAttributes attributes)
568 string inside_tags;
569 bool old = tokenizer.ExpectAttrValue;
571 tokenizer.ExpectAttrValue = false;
572 if (Eat ('@')){
573 tokenizer.ExpectAttrValue = old;
574 tagtype = TagType.Directive;
575 id = "";
576 if (Eat (Token.DIRECTIVE))
577 id = tokenizer.Value;
579 attributes = GetAttributes ();
580 if (!Eat ('%') || !Eat ('>'))
581 OnError ("expecting '%>'");
583 return;
586 if (Eat (Token.DOUBLEDASH)) {
587 tokenizer.ExpectAttrValue = old;
588 tokenizer.Verbatim = true;
589 inside_tags = GetVerbatim (tokenizer.get_token (), "--%>");
590 tokenizer.Verbatim = false;
591 id = null;
592 attributes = null;
593 tagtype = TagType.ServerComment;
594 return;
597 tokenizer.ExpectAttrValue = old;
598 bool varname;
599 bool databinding;
600 #if NET_4_0
601 bool codeRenderEncode;
602 #endif
603 varname = Eat ('=');
604 databinding = !varname && Eat ('#');
605 #if NET_4_0
606 codeRenderEncode = !databinding && !varname && Eat (':');
607 #endif
608 string odds = tokenizer.Odds;
610 tokenizer.Verbatim = true;
611 inside_tags = GetVerbatim (tokenizer.get_token (), "%>");
612 if (databinding && odds != null && odds.Length > 0) {
613 databinding = false;
615 // We encountered <% #something here %>, this should be passed
616 // verbatim to the compiler
617 inside_tags = '#' + inside_tags;
620 tokenizer.Verbatim = false;
621 id = inside_tags;
622 attributes = null;
623 if (databinding)
624 tagtype = TagType.DataBinding;
625 else if (varname)
626 tagtype = TagType.CodeRenderExpression;
627 #if NET_4_0
628 else if (codeRenderEncode)
629 tagtype = TagType.CodeRenderEncode;
630 #endif
631 else
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);
640 sb.Append ('}');
642 return sb.ToString ();
645 void OnError (string msg)
647 ParseErrorHandler eh = events [errorEvent] as ParseErrorHandler;
648 if (eh != null)
649 eh (this, msg);
652 void OnTagParsed (TagType tagtype, string id, TagAttributes attributes)
654 TagParsedHandler eh = events [tagParsedEvent] as TagParsedHandler;
655 if (eh != null)
656 eh (this, tagtype, id, attributes);
659 void OnTextParsed (string text)
661 TextParsedHandler eh = events [textParsedEvent] as TextParsedHandler;
662 if (eh != null)
663 eh (this, text);
666 void OnParsingComplete ()
668 ParsingCompleteHandler eh = events [parsingCompleteEvent] as ParsingCompleteHandler;
669 if (eh != null)
670 eh ();