2007-03-13 Jonathan Chambers <joncham@gmail.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AspParser.cs
1 //
2 // System.Web.Compilation.AspParser
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
8 //
9
10 //
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:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
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.
29 //
30 using System;
31 using System.Collections;
32 using System.Globalization;
33 using System.IO;
34 using System.Text;
35 using System.Web.Util;
36
37 namespace System.Web.Compilation
38 {
39         delegate void ParseErrorHandler (ILocation location, string message);
40         delegate void TextParsedHandler (ILocation location, string text);
41         delegate void TagParsedHandler (ILocation location, TagType tagtype, string id, TagAttributes attributes);
42
43         class AspParser : ILocation
44         {
45                 AspTokenizer tokenizer;
46                 int beginLine, endLine;
47                 int beginColumn, endColumn;
48                 int beginPosition, endPosition;
49                 string filename;
50                 string fileText;
51                 string verbatimID;
52
53                 public AspParser (string filename, TextReader input)
54                 {
55                         this.filename = filename;
56                         fileText = input.ReadToEnd ();
57                         StringReader reader = new StringReader (fileText);
58                         tokenizer = new AspTokenizer (reader);
59                 }
60
61                 public int BeginLine {
62                         get { return beginLine; }
63                 }
64
65                 public int BeginColumn {
66                         get { return beginColumn; }
67                 }
68
69                 public int EndLine {
70                         get { return endLine; }
71                 }
72
73                 public int EndColumn {
74                         get { return endColumn; }
75                 }
76
77                 public string PlainText {
78                         get {
79                                 if (beginPosition >= endPosition)
80                                         return null;
81
82                                 return fileText.Substring (beginPosition, endPosition - beginPosition);
83                         }
84                 }
85
86                 public string Filename {
87                         get { return filename; }
88                 }
89
90                 public string VerbatimID {
91                         set {
92                                 tokenizer.Verbatim = true;
93                                 verbatimID = value;
94                         }
95                 }
96                 
97                 bool Eat (int expected_token)
98                 {
99                         if (tokenizer.get_token () != expected_token) {
100                                 tokenizer.put_back ();
101                                 return false;
102                         }
103
104                         endLine = tokenizer.EndLine;
105                         endColumn = tokenizer.EndColumn;
106                         return true;
107                 }
108
109                 void BeginElement ()
110                 {
111                         beginLine = tokenizer.BeginLine;
112                         beginColumn = tokenizer.BeginColumn;
113                         beginPosition = tokenizer.Position - 1;
114                 }
115
116                 void EndElement ()
117                 {
118                         endLine = tokenizer.EndLine;
119                         endColumn = tokenizer.EndColumn;
120                         endPosition = tokenizer.Position;
121                 }
122
123                 public void Parse ()
124                 {
125                         int token;
126                         string id;
127                         TagAttributes attributes;
128                         TagType tagtype = TagType.Text;
129                         StringBuilder text =  new StringBuilder ();
130
131                         while ((token = tokenizer.get_token ()) != Token.EOF) {
132                                 BeginElement ();
133
134                                 if (tokenizer.Verbatim){
135                                         string end_verbatim = "</" + verbatimID + ">";
136                                         string verbatim_text = GetVerbatim (token, end_verbatim);
137
138                                         if (verbatim_text == null)
139                                                 OnError ("Unexpected EOF processing " + verbatimID);
140
141                                         tokenizer.Verbatim = false;
142
143                                         EndElement ();
144                                         endPosition -= end_verbatim.Length;
145                                         OnTextParsed (verbatim_text);
146                                         beginPosition = endPosition;
147                                         endPosition += end_verbatim.Length;
148                                         OnTagParsed (TagType.Close, verbatimID, null);
149                                         continue;
150                                 }
151                                 
152                                 if (token == '<') {
153                                         GetTag (out tagtype, out id, out attributes);
154                                         EndElement ();
155                                         if (tagtype == TagType.ServerComment)
156                                                 continue;
157
158                                         if (tagtype == TagType.Text)
159                                                 OnTextParsed (id);
160                                         else
161                                                 OnTagParsed (tagtype, id, attributes);
162
163                                         continue;
164                                 }
165
166                                 if (tokenizer.Value.Trim () == "" && tagtype == TagType.Directive) {
167                                         continue;
168                                 }
169
170                                 text.Length = 0;
171                                 do {
172                                         text.Append (tokenizer.Value);
173                                         token = tokenizer.get_token ();
174                                 } while (token != '<' && token != Token.EOF);
175
176                                 tokenizer.put_back ();
177                                 EndElement ();
178                                 OnTextParsed (text.ToString ());
179                         }
180                 }
181
182                 bool GetInclude (string str, out string pathType, out string filename)
183                 {
184                         pathType = null;
185                         filename = null;
186                         str = str.Substring (2).Trim ();
187                         int len = str.Length;
188                         int lastQuote = str.LastIndexOf ('"');
189                         if (len < 10 || lastQuote != len - 1)
190                                 return false;
191
192                         if (!StrUtils.StartsWith (str, "#include ", true))
193                                 return false;
194
195                         str = str.Substring (9).Trim ();
196                         bool isfile = (StrUtils.StartsWith (str ,"file", true));
197                         if (!isfile && !StrUtils.StartsWith (str, "virtual", true))
198                                 return false;
199
200                         pathType = (isfile) ? "file" : "virtual";
201                         if (str.Length < pathType.Length + 3)
202                                 return false;
203
204                         str = str.Substring (pathType.Length).Trim ();
205                         if (str.Length < 3 || str [0] != '=')
206                                 return false;
207
208                         int index = 1;
209                         for (; index < str.Length; index++) {
210                                 if (Char.IsWhiteSpace (str [index]))
211                                         continue;
212                                 else if (str [index] == '"')
213                                         break;
214                         }
215
216                         if (index == str.Length || index == lastQuote)
217                                 return false;
218
219                         str = str.Substring (index);
220                         if (str.Length == 2) { // only quotes
221                                 OnError ("Empty file name.");
222                                 return false;
223                         }
224
225                         filename = str.Trim ().Substring (index, str.Length - 2);
226                         if (filename.LastIndexOf  ('"') != -1)
227                                 return false; // file=""" -> no error
228
229                         return true;
230                 }
231
232                 void GetTag (out TagType tagtype, out string id, out TagAttributes attributes)
233                 {
234                         int token = tokenizer.get_token ();
235
236                         tagtype = TagType.ServerComment;
237                         id = null;
238                         attributes = null;
239                         switch (token){
240                         case '%':
241                                 GetServerTag (out tagtype, out id, out attributes);
242                                 break;
243                         case '/':
244                                 if (!Eat (Token.IDENTIFIER))
245                                         OnError ("expecting TAGNAME");
246
247                                 id = tokenizer.Value;
248                                 if (!Eat ('>'))
249                                         OnError ("expecting '>'. Got '" + id + "'");
250
251                                 tagtype = TagType.Close;
252                                 break;
253                         case '!':
254                                 bool double_dash = Eat (Token.DOUBLEDASH);
255                                 if (double_dash)
256                                         tokenizer.put_back ();
257
258                                 tokenizer.Verbatim = true;
259                                 string end = double_dash ? "-->" : ">";
260                                 string comment = GetVerbatim (tokenizer.get_token (), end);
261                                 tokenizer.Verbatim = false;
262                                 if (comment == null)
263                                         OnError ("Unfinished HTML comment/DTD");
264
265                                 string pathType, filename;
266                                 if (double_dash && GetInclude (comment, out pathType, out filename)) {
267                                         tagtype = TagType.Include;
268                                         attributes = new TagAttributes ();
269                                         attributes.Add (pathType, filename);
270                                 } else {
271                                         tagtype = TagType.Text;
272                                         id = "<!" + comment + end;
273                                 }
274                                 break;
275                         case Token.IDENTIFIER:
276                                 if (this.filename == "@@inner_string@@") {
277                                         // Actually not tag but "xxx < yyy" stuff in inner_string!
278                                         tagtype = TagType.Text;
279                                         tokenizer.InTag = false;
280                                         id = "<" + tokenizer.Odds + tokenizer.Value;
281                                 } else {
282                                         id = tokenizer.Value;
283                                         try {
284                                                 attributes = GetAttributes ();
285                                         } catch (Exception e) {
286                                                 OnError (e.Message);
287                                                 break;
288                                         }
289                                         
290                                         tagtype = TagType.Tag;
291                                         if (Eat ('/') && Eat ('>')) {
292                                                 tagtype = TagType.SelfClosing;
293                                         } else if (!Eat ('>')) {
294                                                 if (attributes.IsRunAtServer ()) {
295                                                         OnError ("The server tag is not well formed.");
296                                                         break;
297                                                 }
298                                                 tokenizer.Verbatim = true;
299                                                 attributes.Add ("", GetVerbatim (tokenizer.get_token (), ">") + ">");
300                                                 tokenizer.Verbatim = false;
301                                         }
302                                 }
303
304                                 break;
305                         default:
306                                 tagtype = TagType.Text;
307                                 tokenizer.InTag = false;
308                                 id = "<" + tokenizer.Value;
309                                 break;
310                         }
311                 }
312
313                 TagAttributes GetAttributes ()
314                 {
315                         int token;
316                         TagAttributes attributes;
317                         string id;
318                         bool wellFormedForServer = true;
319
320                         attributes = new TagAttributes ();
321                         while ((token = tokenizer.get_token ()) != Token.EOF){
322                                 if (token == '<' && Eat ('%')) {
323                                         tokenizer.Verbatim = true;
324                                         attributes.Add ("", "<%" + 
325                                                         GetVerbatim (tokenizer.get_token (), "%>") + "%>");
326                                         tokenizer.Verbatim = false;
327                                         tokenizer.InTag = true;
328                                         continue;
329                                 }
330                                         
331                                 if (token != Token.IDENTIFIER)
332                                         break;
333
334                                 id = tokenizer.Value;
335                                 if (Eat ('=')){
336                                         if (Eat (Token.ATTVALUE)){
337                                                 attributes.Add (id, tokenizer.Value);
338                                                 wellFormedForServer &= tokenizer.AlternatingQuotes;
339                                         } else if (Eat ('<') && Eat ('%')) {
340                                                 tokenizer.Verbatim = true;
341                                                 attributes.Add (id, "<%" + 
342                                                                 GetVerbatim (tokenizer.get_token (), "%>") + "%>");
343                                                 tokenizer.Verbatim = false;
344                                                 tokenizer.InTag = true;
345                                         } else {
346                                                 OnError ("expected ATTVALUE");
347                                                 return null;
348                                         }
349                                 } else {
350                                         attributes.Add (id, null);
351                                 }
352                         }
353
354                         tokenizer.put_back ();
355
356                         if (attributes.IsRunAtServer () && !wellFormedForServer) {
357                                 OnError ("The server tag is not well formed.");
358                                 return null;
359                         }
360                         
361                         return attributes;
362                 }
363
364                 string GetVerbatim (int token, string end)
365                 {
366                         StringBuilder vb_text = new StringBuilder ();
367                         int i = 0;
368
369                         if (tokenizer.Value.Length > 1){
370                                 // May be we have a put_back token that is not a single character
371                                 vb_text.Append (tokenizer.Value);
372                                 token = tokenizer.get_token ();
373                         }
374
375                         end = end.ToLower (CultureInfo.InvariantCulture);
376                         while (token != Token.EOF){
377                                 if (Char.ToLower ((char) token, CultureInfo.InvariantCulture) == end [i]){
378                                         if (++i >= end.Length)
379                                                 break;
380                                         token = tokenizer.get_token ();
381                                         continue;
382                                 } else if (i > 0) {
383                                         for (int j = 0; j < i; j++)
384                                                 vb_text.Append (end [j]);
385                                         i = 0;
386                                 }
387
388                                 vb_text.Append ((char) token);
389                                 token = tokenizer.get_token ();
390                         } 
391
392                         if (token == Token.EOF)
393                                 OnError ("Expecting " + end + " and got EOF.");
394
395                         return RemoveComments (vb_text.ToString ());
396                 }
397
398                 string RemoveComments (string text)
399                 {
400                         int end;
401                         int start = text.IndexOf ("<%--");
402
403                         while (start != -1) {
404                                 end = text.IndexOf ("--%>");
405                                 if (end == -1 || end <= start + 1)
406                                         break;
407
408                                 text = text.Remove (start, end - start + 4);
409                                 start = text.IndexOf ("<%--");
410                         }
411
412                         return text;
413                 }
414
415                 void GetServerTag (out TagType tagtype, out string id, out TagAttributes attributes)
416                 {
417                         string inside_tags;
418                         bool old = tokenizer.ExpectAttrValue;
419
420                         tokenizer.ExpectAttrValue = false;
421                         if (Eat ('@')){
422                                 tokenizer.ExpectAttrValue = old;
423                                 tagtype = TagType.Directive;
424                                 id = "";
425                                 if (Eat (Token.DIRECTIVE))
426                                         id = tokenizer.Value;
427
428                                 attributes = GetAttributes ();
429                                 if (!Eat ('%') || !Eat ('>'))
430                                         OnError ("expecting '%>'");
431
432                                 return;
433                         }
434                         
435                         if (Eat (Token.DOUBLEDASH)) {
436                                 tokenizer.ExpectAttrValue = old;
437                                 tokenizer.Verbatim = true;
438                                 inside_tags = GetVerbatim (tokenizer.get_token (), "--%>");
439                                 tokenizer.Verbatim = false;
440                                 id = null;
441                                 attributes = null;
442                                 tagtype = TagType.ServerComment;
443                                 return;
444                         }
445
446                         tokenizer.ExpectAttrValue = old;
447                         bool varname;
448                         bool databinding;
449                         varname = Eat ('=');
450                         databinding = !varname && Eat ('#');
451
452                         tokenizer.Verbatim = true;
453                         inside_tags = GetVerbatim (tokenizer.get_token (), "%>");
454                         tokenizer.Verbatim = false;
455                         id = inside_tags;
456                         attributes = null;
457                         tagtype = (databinding ? TagType.DataBinding :
458                                   (varname ? TagType.CodeRenderExpression : TagType.CodeRender));
459                 }
460
461                 public event ParseErrorHandler Error;
462                 public event TagParsedHandler TagParsed;
463                 public event TextParsedHandler TextParsed;
464
465                 void OnError (string msg)
466                 {
467                         if (Error != null)
468                                 Error (this, msg);
469                 }
470
471                 void OnTagParsed (TagType tagtype, string id, TagAttributes attributes)
472                 {
473                         if (TagParsed != null)
474                                 TagParsed (this, tagtype, id, attributes);
475                 }
476
477                 void OnTextParsed (string text)
478                 {
479                         if (TextParsed != null)
480                                 TextParsed (this, text);
481                 }
482         }
483
484 }
485