* support-test-*.cs: Rename from test-*-p2.cs.
[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                                         index++;
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
319                         attributes = new TagAttributes ();
320                         while ((token = tokenizer.get_token ()) != Token.EOF){
321                                 if (token == '<' && Eat ('%')) {
322                                         tokenizer.Verbatim = true;
323                                         attributes.Add ("", "<%" + 
324                                                         GetVerbatim (tokenizer.get_token (), "%>") + "%>");
325                                         tokenizer.Verbatim = false;
326                                         tokenizer.InTag = true;
327                                         continue;
328                                 }
329                                         
330                                 if (token != Token.IDENTIFIER)
331                                         break;
332
333                                 id = tokenizer.Value;
334                                 if (Eat ('=')){
335                                         if (Eat (Token.ATTVALUE)){
336                                                 attributes.Add (id, tokenizer.Value);
337                                         } else if (Eat ('<') && Eat ('%')) {
338                                                 tokenizer.Verbatim = true;
339                                                 attributes.Add (id, "<%" + 
340                                                                 GetVerbatim (tokenizer.get_token (), "%>") + "%>");
341                                                 tokenizer.Verbatim = false;
342                                                 tokenizer.InTag = true;
343                                         } else {
344                                                 OnError ("expected ATTVALUE");
345                                                 return null;
346                                         }
347                                 } else {
348                                         attributes.Add (id, null);
349                                 }
350                         }
351
352                         tokenizer.put_back ();
353                         return attributes;
354                 }
355
356                 string GetVerbatim (int token, string end)
357                 {
358                         StringBuilder vb_text = new StringBuilder ();
359                         int i = 0;
360
361                         if (tokenizer.Value.Length > 1){
362                                 // May be we have a put_back token that is not a single character
363                                 vb_text.Append (tokenizer.Value);
364                                 token = tokenizer.get_token ();
365                         }
366
367                         end = end.ToLower (CultureInfo.InvariantCulture);
368                         while (token != Token.EOF){
369                                 if (Char.ToLower ((char) token, CultureInfo.InvariantCulture) == end [i]){
370                                         if (++i >= end.Length)
371                                                 break;
372                                         token = tokenizer.get_token ();
373                                         continue;
374                                 } else if (i > 0) {
375                                         for (int j = 0; j < i; j++)
376                                                 vb_text.Append (end [j]);
377                                         i = 0;
378                                 }
379
380                                 vb_text.Append ((char) token);
381                                 token = tokenizer.get_token ();
382                         } 
383
384                         if (token == Token.EOF)
385                                 OnError ("Expecting " + end + " and got EOF.");
386
387                         return RemoveComments (vb_text.ToString ());
388                 }
389
390                 string RemoveComments (string text)
391                 {
392                         int end;
393                         int start = text.IndexOf ("<%--");
394
395                         while (start != -1) {
396                                 end = text.IndexOf ("--%>");
397                                 if (end == -1 || end <= start + 1)
398                                         break;
399
400                                 text = text.Remove (start, end - start + 4);
401                                 start = text.IndexOf ("<%--");
402                         }
403
404                         return text;
405                 }
406
407                 void GetServerTag (out TagType tagtype, out string id, out TagAttributes attributes)
408                 {
409                         string inside_tags;
410                         bool old = tokenizer.ExpectAttrValue;
411
412                         tokenizer.ExpectAttrValue = false;
413                         if (Eat ('@')){
414                                 tokenizer.ExpectAttrValue = old;
415                                 tagtype = TagType.Directive;
416                                 id = "";
417                                 if (Eat (Token.DIRECTIVE))
418                                         id = tokenizer.Value;
419
420                                 attributes = GetAttributes ();
421                                 if (!Eat ('%') || !Eat ('>'))
422                                         OnError ("expecting '%>'");
423
424                                 return;
425                         }
426                         
427                         if (Eat (Token.DOUBLEDASH)) {
428                                 tokenizer.ExpectAttrValue = old;
429                                 tokenizer.Verbatim = true;
430                                 inside_tags = GetVerbatim (tokenizer.get_token (), "--%>");
431                                 tokenizer.Verbatim = false;
432                                 id = null;
433                                 attributes = null;
434                                 tagtype = TagType.ServerComment;
435                                 return;
436                         }
437
438                         tokenizer.ExpectAttrValue = old;
439                         bool varname;
440                         bool databinding;
441                         varname = Eat ('=');
442                         databinding = !varname && Eat ('#');
443
444                         tokenizer.Verbatim = true;
445                         inside_tags = GetVerbatim (tokenizer.get_token (), "%>");
446                         tokenizer.Verbatim = false;
447                         id = inside_tags;
448                         attributes = null;
449                         tagtype = (databinding ? TagType.DataBinding :
450                                   (varname ? TagType.CodeRenderExpression : TagType.CodeRender));
451                 }
452
453                 public event ParseErrorHandler Error;
454                 public event TagParsedHandler TagParsed;
455                 public event TextParsedHandler TextParsed;
456
457                 void OnError (string msg)
458                 {
459                         if (Error != null)
460                                 Error (this, msg);
461                 }
462
463                 void OnTagParsed (TagType tagtype, string id, TagAttributes attributes)
464                 {
465                         if (TagParsed != null)
466                                 TagParsed (this, tagtype, id, attributes);
467                 }
468
469                 void OnTextParsed (string text)
470                 {
471                         if (TextParsed != null)
472                                 TextParsed (this, text);
473                 }
474         }
475
476 }
477