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 ("", 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 ("", "<%" +
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;