corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 #region BSD License
2 /*
3 Copyright (c) 2004-2005 Matthew Holmes (matthew@wildfiregames.com), Dan Moorehead (dan05a@gmail.com)
4  
5 Redistribution and use in source and binary forms, with or without modification, are permitted
6 provided that the following conditions are met:
7  
8 * Redistributions of source code must retain the above copyright notice, this list of conditions
9 and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
11 and the following disclaimer in the documentation and/or other materials provided with the
12 distribution.
13 * The name of the author may not be used to endorse or promote products derived from this software
14 without specific prior written permission.
15  
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
17 BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24 #endregion
25  
26 #region CVS Information
27 /*
28 * $Source$
29 * $Author: dmoonfire $
30 * $Date: 2008-12-09 18:26:03 -0800 (Tue, 09 Dec 2008) $
31 * $Revision: 280 $
32 */
33 #endregion
34  
35 using System;
36 using System.Collections;
37 using System.IO;
38 using System.Text.RegularExpressions;
39 using System.Xml;
40  
41 namespace Prebuild.Core.Parse
42 {
43 /// <summary>
44 ///
45 /// </summary>
46 public enum OperatorSymbol
47 {
48 /// <summary>
49 ///
50 /// </summary>
51 None,
52 /// <summary>
53 ///
54 /// </summary>
55 Equal,
56 /// <summary>
57 ///
58 /// </summary>
59 NotEqual,
60 /// <summary>
61 ///
62 /// </summary>
63 LessThan,
64 /// <summary>
65 ///
66 /// </summary>
67 GreaterThan,
68 /// <summary>
69 ///
70 /// </summary>
71 LessThanEqual,
72 /// <summary>
73 ///
74 /// </summary>
75 GreaterThanEqual
76 }
77  
78 /// <summary>
79 ///
80 /// </summary>
81 public class Preprocessor
82 {
83 #region Constants
84  
85 /// <summary>
86 /// Includes the regex to look for file tags in the <?include
87 /// ?> processing instruction.
88 /// </summary>
89 private static readonly Regex includeFileRegex = new Regex("file=\"(.+?)\"");
90  
91 #endregion
92  
93 #region Fields
94  
95 XmlDocument m_OutDoc;
96 Stack m_IfStack;
97 Hashtable m_Variables;
98  
99 #endregion
100  
101 #region Constructors
102  
103 /// <summary>
104 /// Initializes a new instance of the <see cref="Preprocessor"/> class.
105 /// </summary>
106 public Preprocessor()
107 {
108 m_OutDoc = new XmlDocument();
109 m_IfStack = new Stack();
110 m_Variables = new Hashtable();
111  
112 RegisterVariable("OS", GetOS());
113 RegisterVariable("RuntimeVersion", Environment.Version.Major);
114 RegisterVariable("RuntimeMajor", Environment.Version.Major);
115 RegisterVariable("RuntimeMinor", Environment.Version.Minor);
116 RegisterVariable("RuntimeRevision", Environment.Version.Revision);
117 }
118  
119 #endregion
120  
121 #region Properties
122  
123 /// <summary>
124 /// Gets the processed doc.
125 /// </summary>
126 /// <value>The processed doc.</value>
127 public XmlDocument ProcessedDoc
128 {
129 get
130 {
131 return m_OutDoc;
132 }
133 }
134  
135 #endregion
136  
137 #region Private Methods
138  
139 /// <summary>
140 /// Parts of this code were taken from NAnt and is subject to the GPL
141 /// as per NAnt's license. Thanks to the NAnt guys for this little gem.
142 /// </summary>
143 /// <returns></returns>
144 public static string GetOS()
145 {
146 PlatformID platId = Environment.OSVersion.Platform;
147 if(platId == PlatformID.Win32NT || platId == PlatformID.Win32Windows)
148 {
149 return "Win32";
150 }
151  
152 if (File.Exists("/System/Library/Frameworks/Cocoa.framework/Cocoa"))
153 {
154 return "MACOSX";
155 }
156  
157 /*
158 * .NET 1.x, under Mono, the UNIX code is 128. Under
159 * .NET 2.x, Mono or MS, the UNIX code is 4
160 */
161 if(Environment.Version.Major == 1)
162 {
163 if((int)platId == 128)
164 {
165 return "UNIX";
166 }
167 }
168 else if((int)platId == 4)
169 {
170 return "UNIX";
171 }
172  
173 return "Unknown";
174 }
175  
176 private static bool CompareNum(OperatorSymbol oper, int val1, int val2)
177 {
178 switch(oper)
179 {
180 case OperatorSymbol.Equal:
181 return (val1 == val2);
182 case OperatorSymbol.NotEqual:
183 return (val1 != val2);
184 case OperatorSymbol.LessThan:
185 return (val1 < val2);
186 case OperatorSymbol.LessThanEqual:
187 return (val1 <= val2);
188 case OperatorSymbol.GreaterThan:
189 return (val1 > val2);
190 case OperatorSymbol.GreaterThanEqual:
191 return (val1 >= val2);
192 }
193  
194 throw new WarningException("Unknown operator type");
195 }
196  
197 private static bool CompareStr(OperatorSymbol oper, string val1, string val2)
198 {
199 switch(oper)
200 {
201 case OperatorSymbol.Equal:
202 return (val1 == val2);
203 case OperatorSymbol.NotEqual:
204 return (val1 != val2);
205 case OperatorSymbol.LessThan:
206 return (val1.CompareTo(val2) < 0);
207 case OperatorSymbol.LessThanEqual:
208 return (val1.CompareTo(val2) <= 0);
209 case OperatorSymbol.GreaterThan:
210 return (val1.CompareTo(val2) > 0);
211 case OperatorSymbol.GreaterThanEqual:
212 return (val1.CompareTo(val2) >= 0);
213 }
214  
215 throw new WarningException("Unknown operator type");
216 }
217  
218 private static char NextChar(int idx, string str)
219 {
220 if((idx + 1) >= str.Length)
221 {
222 return Char.MaxValue;
223 }
224  
225 return str[idx + 1];
226 }
227 // Very very simple expression parser. Can only match expressions of the form
228 // <var> <op> <value>:
229 // OS = Windows
230 // OS != Linux
231 // RuntimeMinor > 0
232 private bool ParseExpression(string exp)
233 {
234 if(exp == null)
235 {
236 throw new ArgumentException("Invalid expression, cannot be null");
237 }
238  
239 exp = exp.Trim();
240 if(exp.Length < 1)
241 {
242 throw new ArgumentException("Invalid expression, cannot be 0 length");
243 }
244  
245 string id = "";
246 string str = "";
247 OperatorSymbol oper = OperatorSymbol.None;
248 bool inStr = false;
249 char c;
250  
251 for(int i = 0; i < exp.Length; i++)
252 {
253 c = exp[i];
254 if(Char.IsWhiteSpace(c))
255 {
256 continue;
257 }
258  
259 if(Char.IsLetterOrDigit(c) || c == '_')
260 {
261 if(inStr)
262 {
263 str += c;
264 }
265 else
266 {
267 id += c;
268 }
269 }
270 else if(c == '\"')
271 {
272 inStr = !inStr;
273 if(inStr)
274 {
275 str = "";
276 }
277 }
278 else
279 {
280 if(inStr)
281 {
282 str += c;
283 }
284 else
285 {
286 switch(c)
287 {
288 case '=':
289 oper = OperatorSymbol.Equal;
290 break;
291  
292 case '!':
293 if(NextChar(i, exp) == '=')
294 {
295 oper = OperatorSymbol.NotEqual;
296 }
297  
298 break;
299  
300 case '<':
301 if(NextChar(i, exp) == '=')
302 {
303 oper = OperatorSymbol.LessThanEqual;
304 }
305 else
306 {
307 oper = OperatorSymbol.LessThan;
308 }
309  
310 break;
311  
312 case '>':
313 if(NextChar(i, exp) == '=')
314 {
315 oper = OperatorSymbol.GreaterThanEqual;
316 }
317 else
318 {
319 oper = OperatorSymbol.GreaterThan;
320 }
321  
322 break;
323 }
324 }
325 }
326 }
327  
328  
329 if(inStr)
330 {
331 throw new WarningException("Expected end of string in expression");
332 }
333  
334 if(oper == OperatorSymbol.None)
335 {
336 throw new WarningException("Expected operator in expression");
337 }
338 else if(id.Length < 1)
339 {
340 throw new WarningException("Expected identifier in expression");
341 }
342 else if(str.Length < 1)
343 {
344 throw new WarningException("Expected value in expression");
345 }
346  
347 bool ret = false;
348 try
349 {
350 object val = m_Variables[id.ToLower()];
351 if(val == null)
352 {
353 throw new WarningException("Unknown identifier '{0}'", id);
354 }
355  
356 int numVal, numVal2;
357 string strVal, strVal2;
358 Type t = val.GetType();
359 if(t.IsAssignableFrom(typeof(int)))
360 {
361 numVal = (int)val;
362 numVal2 = Int32.Parse(str);
363 ret = CompareNum(oper, numVal, numVal2);
364 }
365 else
366 {
367 strVal = val.ToString();
368 strVal2 = str;
369 ret = CompareStr(oper, strVal, strVal2);
370 }
371 }
372 catch(ArgumentException ex)
373 {
374 ex.ToString();
375 throw new WarningException("Invalid value type for system variable '{0}', expected int", id);
376 }
377  
378 return ret;
379 }
380  
381 #endregion
382  
383 #region Public Methods
384  
385 /// <summary>
386 ///
387 /// </summary>
388 /// <param name="name"></param>
389 /// <param name="variableValue"></param>
390 public void RegisterVariable(string name, object variableValue)
391 {
392 if(name == null || variableValue == null)
393 {
394 return;
395 }
396  
397 m_Variables[name.ToLower()] = variableValue;
398 }
399  
400 /// <summary>
401 /// Performs validation on the xml source as well as evaluates conditional and flow expresions
402 /// </summary>
403 /// <exception cref="ArgumentException">For invalid use of conditional expressions or for invalid XML syntax. If a XmlValidatingReader is passed, then will also throw exceptions for non-schema-conforming xml</exception>
404 /// <param name="reader"></param>
405 /// <returns>the output xml </returns>
406 public string Process(XmlReader initialReader)
407 {
408 if(initialReader == null)
409 {
410 throw new ArgumentException("Invalid XML reader to pre-process");
411 }
412  
413 IfContext context = new IfContext(true, true, IfState.None);
414 StringWriter xmlText = new StringWriter();
415 XmlTextWriter writer = new XmlTextWriter(xmlText);
416 writer.Formatting = Formatting.Indented;
417  
418 // Create a queue of XML readers and add the initial
419 // reader to it. Then we process until we run out of
420 // readers which lets the <?include?> operation add more
421 // readers to generate a multi-file parser and not require
422 // XML fragments that a recursive version would use.
423 Stack readerStack = new Stack();
424 readerStack.Push(initialReader);
425  
426 while(readerStack.Count > 0)
427 {
428 // Pop off the next reader.
429 XmlReader reader = (XmlReader) readerStack.Pop();
430  
431 // Process through this XML reader until it is
432 // completed (or it is replaced by the include
433 // operation).
434 while(reader.Read())
435 {
436 // The prebuild file has a series of processing
437 // instructions which allow for specific
438 // inclusions based on operating system or to
439 // include additional files.
440 if(reader.NodeType == XmlNodeType.ProcessingInstruction)
441 {
442 bool ignore = false;
443  
444 switch(reader.LocalName)
445 {
446 case "include":
447 // use regular expressions to parse out the attributes.
448 MatchCollection matches = includeFileRegex.Matches(reader.Value);
449  
450 // make sure there is only one file attribute.
451 if(matches.Count > 1)
452 {
453 throw new WarningException("An <?include ?> node was found, but it specified more than one file.");
454 }
455  
456 if(matches.Count == 0)
457 {
458 throw new WarningException("An <?include ?> node was found, but it did not specify the file attribute.");
459 }
460  
461 // Pull the file out from the regex and make sure it is a valid file before using it.
462 string filename = matches[0].Groups[1].Value;
463 FileInfo includeFile = new FileInfo(filename);
464  
465 if(!includeFile.Exists)
466 {
467 throw new WarningException("Cannot include file: " + includeFile.FullName);
468 }
469  
470 // Create a new reader object for this file. Then put the old reader back on the stack and start
471 // processing using this new XML reader.
472 XmlReader newReader = new XmlTextReader(includeFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read));
473  
474 readerStack.Push(reader);
475 reader = newReader;
476 ignore = true;
477 break;
478  
479 case "if":
480 m_IfStack.Push(context);
481 context = new IfContext(context.Keep & context.Active, ParseExpression(reader.Value), IfState.If);
482 ignore = true;
483 break;
484  
485 case "elseif":
486 if(m_IfStack.Count == 0)
487 {
488 throw new WarningException("Unexpected 'elseif' outside of 'if'");
489 }
490 else if(context.State != IfState.If && context.State != IfState.ElseIf)
491 {
492 throw new WarningException("Unexpected 'elseif' outside of 'if'");
493 }
494  
495 context.State = IfState.ElseIf;
496 if(!context.EverKept)
497 {
498 context.Keep = ParseExpression(reader.Value);
499 }
500 else
501 {
502 context.Keep = false;
503 }
504  
505 ignore = true;
506 break;
507  
508 case "else":
509 if(m_IfStack.Count == 0)
510 {
511 throw new WarningException("Unexpected 'else' outside of 'if'");
512 }
513 else if(context.State != IfState.If && context.State != IfState.ElseIf)
514 {
515 throw new WarningException("Unexpected 'else' outside of 'if'");
516 }
517  
518 context.State = IfState.Else;
519 context.Keep = !context.EverKept;
520 ignore = true;
521 break;
522  
523 case "endif":
524 if(m_IfStack.Count == 0)
525 {
526 throw new WarningException("Unexpected 'endif' outside of 'if'");
527 }
528  
529 context = (IfContext)m_IfStack.Pop();
530 ignore = true;
531 break;
532 }
533  
534 if(ignore)
535 {
536 continue;
537 }
538 }//end pre-proc instruction
539  
540 if(!context.Active || !context.Keep)
541 {
542 continue;
543 }
544  
545 switch(reader.NodeType)
546 {
547 case XmlNodeType.Element:
548 bool empty = reader.IsEmptyElement;
549 writer.WriteStartElement(reader.Name);
550  
551 while (reader.MoveToNextAttribute())
552 {
553 writer.WriteAttributeString(reader.Name, reader.Value);
554 }
555  
556 if(empty)
557 {
558 writer.WriteEndElement();
559 }
560  
561 break;
562  
563 case XmlNodeType.EndElement:
564 writer.WriteEndElement();
565 break;
566  
567 case XmlNodeType.Text:
568 writer.WriteString(reader.Value);
569 break;
570  
571 case XmlNodeType.CDATA:
572 writer.WriteCData(reader.Value);
573 break;
574  
575 default:
576 break;
577 }
578 }
579  
580 if(m_IfStack.Count != 0)
581 {
582 throw new WarningException("Mismatched 'if', 'endif' pair");
583 }
584 }
585  
586 return xmlText.ToString();
587 }
588  
589 #endregion
590 }
591 }