2003-10-14 Gonzalo Paniagua Javier <gonzalo@ximian.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 using System;
10 using System.Collections;
11 using System.IO;
12 using System.Text;
13
14 namespace System.Web.Compilation
15 {
16         delegate void ParseErrorHandler (ILocation location, string message);
17         delegate void TextParsedHandler (ILocation location, string text);
18         delegate void TagParsedHandler (ILocation location, TagType tagtype, string id, TagAttributes attributes);
19
20         class AspParser : ILocation
21         {
22                 AspTokenizer tokenizer;
23                 int beginLine, endLine;
24                 int beginColumn, endColumn;
25                 int beginPosition, endPosition;
26                 string filename;
27                 string fileText;
28                 string verbatimID;
29
30                 public AspParser (string filename, TextReader input)
31                 {
32                         this.filename = filename;
33                         fileText = input.ReadToEnd ();
34                         StringReader reader = new StringReader (fileText);
35                         tokenizer = new AspTokenizer (reader);
36                 }
37
38                 public int BeginLine {
39                         get { return beginLine; }
40                 }
41
42                 public int BeginColumn {
43                         get { return beginColumn; }
44                 }
45
46                 public int EndLine {
47                         get { return endLine; }
48                 }
49
50                 public int EndColumn {
51                         get { return endColumn; }
52                 }
53
54                 public string PlainText {
55                         get {
56                                 if (beginPosition >= endPosition)
57                                         return null;
58
59                                 return fileText.Substring (beginPosition, endPosition - beginPosition);
60                         }
61                 }
62
63                 public string Filename {
64                         get { return filename; }
65                 }
66
67                 public string VerbatimID {
68                         set {
69                                 tokenizer.Verbatim = true;
70                                 verbatimID = value.ToUpper ();
71                         }
72                 }
73                 
74                 bool Eat (int expected_token)
75                 {
76                         if (tokenizer.get_token () != expected_token) {
77                                 tokenizer.put_back ();
78                                 return false;
79                         }
80
81                         endLine = tokenizer.EndLine;
82                         endColumn = tokenizer.EndColumn;
83                         return true;
84                 }
85
86                 void BeginElement ()
87                 {
88                         beginLine = tokenizer.BeginLine;
89                         beginColumn = tokenizer.BeginColumn;
90                         beginPosition = tokenizer.Position - 1;
91                 }
92
93                 void EndElement ()
94                 {
95                         endLine = tokenizer.EndLine;
96                         endColumn = tokenizer.EndColumn;
97                         endPosition = tokenizer.Position;
98                 }
99
100                 public void Parse ()
101                 {
102                         int token;
103                         string id;
104                         TagAttributes attributes;
105                         TagType tagtype;
106                         StringBuilder text =  new StringBuilder ();
107
108                         while ((token = tokenizer.get_token ()) != Token.EOF) {
109                                 BeginElement ();
110
111                                 if (tokenizer.Verbatim){
112                                         string end_verbatim = "</" + verbatimID + ">";
113                                         string verbatim_text = GetVerbatim (token, end_verbatim);
114
115                                         if (verbatim_text == null)
116                                                 OnError ("Unexpected EOF processing " + verbatimID);
117
118                                         tokenizer.Verbatim = false;
119
120                                         EndElement ();
121                                         endPosition -= end_verbatim.Length;
122                                         OnTextParsed (verbatim_text);
123                                         beginPosition = endPosition;
124                                         endPosition += end_verbatim.Length;
125                                         OnTagParsed (TagType.Close, verbatimID, null);
126                                         continue;
127                                 }
128                                 
129                                 if (token == '<') {
130                                         GetTag (out tagtype, out id, out attributes);
131                                         EndElement ();
132                                         if (tagtype == TagType.ServerComment)
133                                                 continue;
134
135                                         if (tagtype == TagType.Text)
136                                                 OnTextParsed (id);
137                                         else
138                                                 OnTagParsed (tagtype, id, attributes);
139
140                                         continue;
141                                 }
142
143                                 text.Length = 0;
144                                 do {
145                                         text.Append (tokenizer.Value);
146                                         token = tokenizer.get_token ();
147                                 } while (token != '<' && token != Token.EOF);
148
149                                 tokenizer.put_back ();
150                                 EndElement ();
151                                 OnTextParsed (text.ToString ());
152                         }
153                 }
154
155                 bool GetInclude (string str, out string pathType, out string filename)
156                 {
157                         pathType = null;
158                         filename = null;
159                         str = str.Substring (2).Trim ();
160                         int len = str.Length;
161                         int lastQuote = str.LastIndexOf ('"');
162                         if (len < 10 || lastQuote != len - 1 || !str.StartsWith ("#include "))
163                                 return false;
164
165                         str = str.Substring (9).Trim ();
166                         bool isfile = (str.StartsWith ("file"));
167                         if (!isfile && !str.StartsWith ("virtual"))
168                                 return false;
169
170                         pathType = (isfile) ? "file" : "virtual";
171                         if (str.Length < pathType.Length + 3)
172                                 return false;
173
174                         str = str.Substring (pathType.Length).Trim ();
175                         if (str.Length < 3 || str [0] != '=')
176                                 return false;
177
178                         int index = 1;
179                         for (; index < str.Length; index++) {
180                                 if (Char.IsWhiteSpace (str [index]))
181                                         index++;
182                                 else if (str [index] == '"')
183                                         break;
184                         }
185
186                         if (index == str.Length || index == lastQuote)
187                                 return false;
188
189                         str = str.Substring (index);
190                         if (str.Length == 2) { // only quotes
191                                 OnError ("Empty file name.");
192                                 return false;
193                         }
194
195                         filename = str.Trim ().Substring (index, str.Length - 2);
196                         if (filename.LastIndexOf  ('"') != -1)
197                                 return false; // file=""" -> no error
198
199                         return true;
200                 }
201
202                 void GetTag (out TagType tagtype, out string id, out TagAttributes attributes)
203                 {
204                         int token = tokenizer.get_token ();
205
206                         tagtype = TagType.ServerComment;
207                         id = null;
208                         attributes = null;
209                         switch (token){
210                         case '%':
211                                 GetServerTag (out tagtype, out id, out attributes);
212                                 break;
213                         case '/':
214                                 if (!Eat (Token.IDENTIFIER))
215                                         OnError ("expecting TAGNAME");
216
217                                 id = tokenizer.Value;
218                                 if (!Eat ('>'))
219                                         OnError ("expecting '>'. Got '" + id + "'");
220
221                                 tagtype = TagType.Close;
222                                 break;
223                         case '!':
224                                 bool double_dash = Eat (Token.DOUBLEDASH);
225                                 if (double_dash)
226                                         tokenizer.put_back ();
227
228                                 tokenizer.Verbatim = true;
229                                 string end = double_dash ? "-->" : ">";
230                                 string comment = GetVerbatim (tokenizer.get_token (), end);
231                                 tokenizer.Verbatim = false;
232                                 if (comment == null)
233                                         OnError ("Unfinished HTML comment/DTD");
234
235                                 string pathType, filename;
236                                 if (double_dash && GetInclude (comment, out pathType, out filename)) {
237                                         tagtype = TagType.Include;
238                                         attributes = new TagAttributes ();
239                                         attributes.Add (pathType, filename);
240                                 } else {
241                                         tagtype = TagType.Text;
242                                         id = "<!" + comment + end;
243                                 }
244                                 break;
245                         case Token.IDENTIFIER:
246                                 id = tokenizer.Value;
247                                 try {
248                                         attributes = GetAttributes ();
249                                 } catch (Exception e) {
250                                         OnError (e.Message);
251                                         break;
252                                 }
253                                 
254                                 tagtype = TagType.Tag;
255                                 if (Eat ('/') && Eat ('>'))
256                                         tagtype = TagType.SelfClosing;
257                                 else if (!Eat ('>'))
258                                         OnError ("expecting '>'. Got '" + tokenizer.Value + "'");
259
260                                 break;
261                         default:
262                                 tagtype = TagType.Text;
263                                 tokenizer.InTag = false;
264                                 id = "<" + tokenizer.Value;
265                                 break;
266                         }
267                 }
268
269                 TagAttributes GetAttributes ()
270                 {
271                         int token;
272                         TagAttributes attributes;
273                         string id;
274
275                         attributes = new TagAttributes ();
276                         while ((token = tokenizer.get_token ()) != Token.EOF){
277                                 if (token != Token.IDENTIFIER)
278                                         break;
279                                 id = tokenizer.Value;
280                                 if (Eat ('=')){
281                                         if (Eat (Token.ATTVALUE)){
282                                                 attributes.Add (id, tokenizer.Value);
283                                         } else {
284                                                 //TODO: support data binding syntax without quotes
285                                                 OnError ("expected ATTVALUE");
286                                                 return null;
287                                         }
288                                         
289                                 } else {
290                                         attributes.Add (id, null);
291                                 }
292                         }
293
294                         tokenizer.put_back ();
295                         return attributes;
296                 }
297
298                 string GetVerbatim (int token, string end)
299                 {
300                         StringBuilder vb_text = new StringBuilder ();
301                         int i = 0;
302
303                         if (tokenizer.Value.Length > 1){
304                                 // May be we have a put_back token that is not a single character
305                                 vb_text.Append (tokenizer.Value);
306                                 token = tokenizer.get_token ();
307                         }
308
309                         while (token != Token.EOF){
310                                 if (Char.ToUpper ((char) token) == end [i]){
311                                         if (++i >= end.Length)
312                                                 break;
313                                         token = tokenizer.get_token ();
314                                         continue;
315                                 } else if (i > 0) {
316                                         for (int j = 0; j < i; j++)
317                                                 vb_text.Append (end [j]);
318                                         i = 0;
319                                 }
320
321                                 vb_text.Append ((char) token);
322                                 token = tokenizer.get_token ();
323                         } 
324
325                         return RemoveComments (vb_text.ToString ());
326                 }
327
328                 string RemoveComments (string text)
329                 {
330                         int end;
331                         int start = text.IndexOf ("<%--");
332
333                         while (start != -1) {
334                                 end = text.IndexOf ("--%>");
335                                 if (end == -1 || end <= start + 1)
336                                         break;
337
338                                 text = text.Remove (start, end - start + 4);
339                                 start = text.IndexOf ("<%--");
340                         }
341
342                         return text;
343                 }
344
345                 void GetServerTag (out TagType tagtype, out string id, out TagAttributes attributes)
346                 {
347                         string inside_tags;
348
349                         if (Eat ('@')){
350                                 tagtype = TagType.Directive;
351                                 id = "";
352                                 if (Eat (Token.DIRECTIVE))
353                                         id = tokenizer.Value;
354
355                                 attributes = GetAttributes ();
356                                 if (!Eat ('%') || !Eat ('>'))
357                                         OnError ("expecting '%>'");
358
359                                 return;
360                         }
361                         
362                         if (Eat (Token.DOUBLEDASH)) {
363                                 tokenizer.Verbatim = true;
364                                 inside_tags = GetVerbatim (tokenizer.get_token (), "--%>");
365                                 tokenizer.Verbatim = false;
366                                 id = null;
367                                 attributes = null;
368                                 tagtype = TagType.ServerComment;
369                                 return;
370                         }
371
372                         bool varname;
373                         bool databinding;
374                         varname = Eat ('=');
375                         databinding = !varname && Eat ('#');
376
377                         tokenizer.Verbatim = true;
378                         inside_tags = GetVerbatim (tokenizer.get_token (), "%>");
379                         tokenizer.Verbatim = false;
380                         id = inside_tags;
381                         attributes = null;
382                         tagtype = (databinding ? TagType.DataBinding :
383                                   (varname ? TagType.CodeRenderExpression : TagType.CodeRender));
384                 }
385
386                 public event ParseErrorHandler Error;
387                 public event TagParsedHandler TagParsed;
388                 public event TextParsedHandler TextParsed;
389
390                 void OnError (string msg)
391                 {
392                         if (Error != null)
393                                 Error (this, msg);
394                 }
395
396                 void OnTagParsed (TagType tagtype, string id, TagAttributes attributes)
397                 {
398                         if (TagParsed != null)
399                                 TagParsed (this, tagtype, id, attributes);
400                 }
401
402                 void OnTextParsed (string text)
403                 {
404                         if (TextParsed != null)
405                                 TextParsed (this, text);
406                 }
407         }
408
409 }
410