clockwerk-opensim – 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 using System;
27 using System.Collections.Generic;
28 using System.IO;
29 using System.Text.RegularExpressions;
30 using System.Xml;
31  
32 namespace Prebuild.Core.Parse
33 {
34 /// <summary>
35 ///
36 /// </summary>
37 public enum OperatorSymbol
38 {
39 /// <summary>
40 ///
41 /// </summary>
42 None,
43 /// <summary>
44 ///
45 /// </summary>
46 Equal,
47 /// <summary>
48 ///
49 /// </summary>
50 NotEqual,
51 /// <summary>
52 ///
53 /// </summary>
54 LessThan,
55 /// <summary>
56 ///
57 /// </summary>
58 GreaterThan,
59 /// <summary>
60 ///
61 /// </summary>
62 LessThanEqual,
63 /// <summary>
64 ///
65 /// </summary>
66 GreaterThanEqual
67 }
68  
69 /// <summary>
70 ///
71 /// </summary>
72 public class Preprocessor
73 {
74 #region Constants
75  
76 /// <summary>
77 /// Includes the regex to look for file tags in the <?include
78 /// ?> processing instruction.
79 /// </summary>
80 private static readonly Regex includeFileRegex = new Regex("file=\"(.+?)\"");
81  
82 #endregion
83  
84 #region Fields
85  
86 readonly XmlDocument m_OutDoc = new XmlDocument();
87 readonly Stack<IfContext> m_IfStack = new Stack<IfContext>();
88 readonly Dictionary<string, object> m_Variables = new Dictionary<string, object>();
89  
90 #endregion
91  
92 #region Constructors
93  
94 /// <summary>
95 /// Initializes a new instance of the <see cref="Preprocessor"/> class.
96 /// </summary>
97 public Preprocessor()
98 {
99 RegisterVariable("OS", GetOS());
100 RegisterVariable("RuntimeVersion", Environment.Version.Major);
101 RegisterVariable("RuntimeMajor", Environment.Version.Major);
102 RegisterVariable("RuntimeMinor", Environment.Version.Minor);
103 RegisterVariable("RuntimeRevision", Environment.Version.Revision);
104 }
105  
106 #endregion
107  
108 #region Properties
109  
110 /// <summary>
111 /// Gets the processed doc.
112 /// </summary>
113 /// <value>The processed doc.</value>
114 public XmlDocument ProcessedDoc
115 {
116 get
117 {
118 return m_OutDoc;
119 }
120 }
121  
122 #endregion
123  
124 #region Private Methods
125  
126 /// <summary>
127 /// Parts of this code were taken from NAnt and is subject to the GPL
128 /// as per NAnt's license. Thanks to the NAnt guys for this little gem.
129 /// </summary>
130 /// <returns></returns>
131 public static string GetOS()
132 {
133 PlatformID platId = Environment.OSVersion.Platform;
134 if(platId == PlatformID.Win32NT || platId == PlatformID.Win32Windows)
135 {
136 return "Win32";
137 }
138  
139 if (File.Exists("/System/Library/Frameworks/Cocoa.framework/Cocoa"))
140 {
141 return "MACOSX";
142 }
143  
144 /*
145 * .NET 1.x, under Mono, the UNIX code is 128. Under
146 * .NET 2.x, Mono or MS, the UNIX code is 4
147 */
148 if(Environment.Version.Major == 1)
149 {
150 if((int)platId == 128)
151 {
152 return "UNIX";
153 }
154 }
155 else if((int)platId == 4)
156 {
157 return "UNIX";
158 }
159  
160 return "Unknown";
161 }
162  
163 private static bool CompareNum(OperatorSymbol oper, int val1, int val2)
164 {
165 switch(oper)
166 {
167 case OperatorSymbol.Equal:
168 return (val1 == val2);
169 case OperatorSymbol.NotEqual:
170 return (val1 != val2);
171 case OperatorSymbol.LessThan:
172 return (val1 < val2);
173 case OperatorSymbol.LessThanEqual:
174 return (val1 <= val2);
175 case OperatorSymbol.GreaterThan:
176 return (val1 > val2);
177 case OperatorSymbol.GreaterThanEqual:
178 return (val1 >= val2);
179 }
180  
181 throw new WarningException("Unknown operator type");
182 }
183  
184 private static bool CompareStr(OperatorSymbol oper, string val1, string val2)
185 {
186 switch(oper)
187 {
188 case OperatorSymbol.Equal:
189 return (val1 == val2);
190 case OperatorSymbol.NotEqual:
191 return (val1 != val2);
192 case OperatorSymbol.LessThan:
193 return (val1.CompareTo(val2) < 0);
194 case OperatorSymbol.LessThanEqual:
195 return (val1.CompareTo(val2) <= 0);
196 case OperatorSymbol.GreaterThan:
197 return (val1.CompareTo(val2) > 0);
198 case OperatorSymbol.GreaterThanEqual:
199 return (val1.CompareTo(val2) >= 0);
200 }
201  
202 throw new WarningException("Unknown operator type");
203 }
204  
205 private static char NextChar(int idx, string str)
206 {
207 if((idx + 1) >= str.Length)
208 {
209 return Char.MaxValue;
210 }
211  
212 return str[idx + 1];
213 }
214 // Very very simple expression parser. Can only match expressions of the form
215 // <var> <op> <value>:
216 // OS = Windows
217 // OS != Linux
218 // RuntimeMinor > 0
219 private bool ParseExpression(string exp)
220 {
221 if(exp == null)
222 {
223 throw new ArgumentException("Invalid expression, cannot be null");
224 }
225  
226 exp = exp.Trim();
227 if(exp.Length < 1)
228 {
229 throw new ArgumentException("Invalid expression, cannot be 0 length");
230 }
231  
232 string id = "";
233 string str = "";
234 OperatorSymbol oper = OperatorSymbol.None;
235 bool inStr = false;
236  
237 for(int i = 0; i < exp.Length; i++)
238 {
239 char c = exp[i];
240 if(Char.IsWhiteSpace(c))
241 {
242 continue;
243 }
244  
245 if(Char.IsLetterOrDigit(c) || c == '_')
246 {
247 if(inStr)
248 {
249 str += c;
250 }
251 else
252 {
253 id += c;
254 }
255 }
256 else if(c == '\"')
257 {
258 inStr = !inStr;
259 if(inStr)
260 {
261 str = "";
262 }
263 }
264 else
265 {
266 if(inStr)
267 {
268 str += c;
269 }
270 else
271 {
272 switch(c)
273 {
274 case '=':
275 oper = OperatorSymbol.Equal;
276 break;
277  
278 case '!':
279 if(NextChar(i, exp) == '=')
280 {
281 oper = OperatorSymbol.NotEqual;
282 }
283  
284 break;
285  
286 case '<':
287 if(NextChar(i, exp) == '=')
288 {
289 oper = OperatorSymbol.LessThanEqual;
290 }
291 else
292 {
293 oper = OperatorSymbol.LessThan;
294 }
295  
296 break;
297  
298 case '>':
299 if(NextChar(i, exp) == '=')
300 {
301 oper = OperatorSymbol.GreaterThanEqual;
302 }
303 else
304 {
305 oper = OperatorSymbol.GreaterThan;
306 }
307  
308 break;
309 }
310 }
311 }
312 }
313  
314  
315 if(inStr)
316 {
317 throw new WarningException("Expected end of string in expression");
318 }
319  
320 if(oper == OperatorSymbol.None)
321 {
322 throw new WarningException("Expected operator in expression");
323 }
324 if(id.Length < 1)
325 {
326 throw new WarningException("Expected identifier in expression");
327 }
328 if(str.Length < 1)
329 {
330 throw new WarningException("Expected value in expression");
331 }
332  
333 bool ret;
334 try
335 {
336 object val = m_Variables[id.ToLower()];
337 if(val == null)
338 {
339 throw new WarningException("Unknown identifier '{0}'", id);
340 }
341  
342 Type t = val.GetType();
343 if(t.IsAssignableFrom(typeof(int)))
344 {
345 int numVal = (int)val;
346 int numVal2 = Int32.Parse(str);
347 ret = CompareNum(oper, numVal, numVal2);
348 }
349 else
350 {
351 string strVal = val.ToString();
352 string strVal2 = str;
353 ret = CompareStr(oper, strVal, strVal2);
354 }
355 }
356 catch(ArgumentException ex)
357 {
358 ex.ToString();
359 throw new WarningException("Invalid value type for system variable '{0}', expected int", id);
360 }
361  
362 return ret;
363 }
364  
365 /// <summary>
366 /// Taken from current Prebuild included in OpenSim 0.7.x
367 /// </summary>
368 /// <param name="readerStack">
369 /// A <see cref="Stack<XmlReader>"/>
370 /// </param>
371 /// <param name="include">
372 /// A <see cref="System.String"/>
373 /// </param>
374 private static void WildCardInclude (Stack<XmlReader> readerStack, string include)
375 {
376 if (!include.Contains ("*")) {
377 return;
378 }
379  
380 // Console.WriteLine("Processing {0}", include);
381  
382 // Break up the include into pre and post wildcard sections
383 string preWildcard = include.Substring (0, include.IndexOf ("*"));
384 string postWildcard = include.Substring (include.IndexOf ("*") + 2);
385  
386 // If preWildcard is a directory, recurse
387 if (Directory.Exists (preWildcard)) {
388 string[] directories = Directory.GetDirectories (preWildcard);
389 Array.Sort (directories);
390 Array.Reverse (directories);
391 foreach (string dirPath in directories) {
392 //Console.WriteLine ("Scanning : {0}", dirPath);
393  
394 string includeFile = Path.Combine (dirPath, postWildcard);
395 if (includeFile.Contains ("*")) {
396 // postWildcard included another wildcard, recurse.
397 WildCardInclude (readerStack, includeFile);
398 } else {
399 FileInfo file = new FileInfo (includeFile);
400 if (file.Exists) {
401 //Console.WriteLine ("Including File: {0}", includeFile);
402 XmlReader newReader = new XmlTextReader (file.Open (FileMode.Open, FileAccess.Read, FileShare.Read));
403 readerStack.Push (newReader);
404 }
405 }
406 }
407 } else {
408 // preWildcard is not a path to a directory, so the wildcard is in the filename
409 string searchFilename = Path.GetFileName (preWildcard.Substring (preWildcard.IndexOf ("/") + 1) + "*" + postWildcard);
410 Console.WriteLine ("searchFilename: {0}", searchFilename);
411  
412 string searchDirectory = Path.GetDirectoryName (preWildcard);
413 Console.WriteLine ("searchDirectory: {0}", searchDirectory);
414  
415 string[] files = Directory.GetFiles (searchDirectory, searchFilename);
416 Array.Sort (files);
417 Array.Reverse (files);
418 foreach (string includeFile in files) {
419 FileInfo file = new FileInfo (includeFile);
420 if (file.Exists) {
421 // Console.WriteLine ("Including File: {0}", includeFile);
422 XmlReader newReader = new XmlTextReader (file.Open (FileMode.Open, FileAccess.Read, FileShare.Read));
423 readerStack.Push (newReader);
424 }
425 }
426 }
427 }
428  
429 #endregion
430  
431 #region Public Methods
432  
433 /// <summary>
434 ///
435 /// </summary>
436 /// <param name="name"></param>
437 /// <param name="variableValue"></param>
438 public void RegisterVariable(string name, object variableValue)
439 {
440 if(name == null || variableValue == null)
441 {
442 return;
443 }
444  
445 m_Variables[name.ToLower()] = variableValue;
446 }
447  
448 /// <summary>
449 /// Performs validation on the xml source as well as evaluates conditional and flow expresions
450 /// </summary>
451 /// <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>
452 /// <param name="initialReader"></param>
453 /// <returns>the output xml </returns>
454 public string Process(XmlReader initialReader)
455 {
456 if(initialReader == null)
457 {
458 throw new ArgumentException("Invalid XML reader to pre-process");
459 }
460  
461 IfContext context = new IfContext(true, true, IfState.None);
462 StringWriter xmlText = new StringWriter();
463 XmlTextWriter writer = new XmlTextWriter(xmlText);
464 writer.Formatting = Formatting.Indented;
465  
466 // Create a queue of XML readers and add the initial
467 // reader to it. Then we process until we run out of
468 // readers which lets the <?include?> operation add more
469 // readers to generate a multi-file parser and not require
470 // XML fragments that a recursive version would use.
471 Stack<XmlReader> readerStack = new Stack<XmlReader>();
472 readerStack.Push(initialReader);
473  
474 while(readerStack.Count > 0)
475 {
476 // Pop off the next reader.
477 XmlReader reader = readerStack.Pop();
478  
479 // Process through this XML reader until it is
480 // completed (or it is replaced by the include
481 // operation).
482 while(reader.Read())
483 {
484 // The prebuild file has a series of processing
485 // instructions which allow for specific
486 // inclusions based on operating system or to
487 // include additional files.
488 if(reader.NodeType == XmlNodeType.ProcessingInstruction)
489 {
490 bool ignore = false;
491  
492 switch(reader.LocalName)
493 {
494 case "include":
495 // use regular expressions to parse out the attributes.
496 MatchCollection matches = includeFileRegex.Matches(reader.Value);
497  
498 // make sure there is only one file attribute.
499 if(matches.Count > 1)
500 {
501 throw new WarningException("An <?include ?> node was found, but it specified more than one file.");
502 }
503  
504 if(matches.Count == 0)
505 {
506 throw new WarningException("An <?include ?> node was found, but it did not specify the file attribute.");
507 }
508  
509 // ***** Adding for wildcard handling
510 // Push current reader back onto the stack.
511 readerStack.Push (reader);
512  
513 // Pull the file out from the regex and make sure it is a valid file before using it.
514 string filename = matches[0].Groups[1].Value;
515  
516 filename = String.Join (Path.DirectorySeparatorChar.ToString (), filename.Split (new char[] { '/', '\\' }));
517  
518 if (!filename.Contains ("*")) {
519  
520 FileInfo includeFile = new FileInfo (filename);
521 if (!includeFile.Exists) {
522 throw new WarningException ("Cannot include file: " + includeFile.FullName);
523 }
524  
525 // Create a new reader object for this file. Then put the old reader back on the stack and start
526 // processing using this new XML reader.
527  
528 XmlReader newReader = new XmlTextReader (includeFile.Open (FileMode.Open, FileAccess.Read, FileShare.Read));
529 reader = newReader;
530 readerStack.Push (reader);
531  
532 } else {
533 WildCardInclude (readerStack, filename);
534 }
535  
536 reader = (XmlReader)readerStack.Pop ();
537 ignore = true;
538 break;
539  
540 case "if":
541 m_IfStack.Push(context);
542 context = new IfContext(context.Keep & context.Active, ParseExpression(reader.Value), IfState.If);
543 ignore = true;
544 break;
545  
546 case "elseif":
547 if(m_IfStack.Count == 0)
548 {
549 throw new WarningException("Unexpected 'elseif' outside of 'if'");
550 }
551 if(context.State != IfState.If && context.State != IfState.ElseIf)
552 {
553 throw new WarningException("Unexpected 'elseif' outside of 'if'");
554 }
555  
556 context.State = IfState.ElseIf;
557 if(!context.EverKept)
558 {
559 context.Keep = ParseExpression(reader.Value);
560 }
561 else
562 {
563 context.Keep = false;
564 }
565  
566 ignore = true;
567 break;
568  
569 case "else":
570 if(m_IfStack.Count == 0)
571 {
572 throw new WarningException("Unexpected 'else' outside of 'if'");
573 }
574 if(context.State != IfState.If && context.State != IfState.ElseIf)
575 {
576 throw new WarningException("Unexpected 'else' outside of 'if'");
577 }
578  
579 context.State = IfState.Else;
580 context.Keep = !context.EverKept;
581 ignore = true;
582 break;
583  
584 case "endif":
585 if(m_IfStack.Count == 0)
586 {
587 throw new WarningException("Unexpected 'endif' outside of 'if'");
588 }
589  
590 context = m_IfStack.Pop();
591 ignore = true;
592 break;
593 }
594  
595 if(ignore)
596 {
597 continue;
598 }
599 }//end pre-proc instruction
600  
601 if(!context.Active || !context.Keep)
602 {
603 continue;
604 }
605  
606 switch(reader.NodeType)
607 {
608 case XmlNodeType.Element:
609 bool empty = reader.IsEmptyElement;
610 writer.WriteStartElement(reader.Name);
611  
612 while (reader.MoveToNextAttribute())
613 {
614 writer.WriteAttributeString(reader.Name, reader.Value);
615 }
616  
617 if(empty)
618 {
619 writer.WriteEndElement();
620 }
621  
622 break;
623  
624 case XmlNodeType.EndElement:
625 writer.WriteEndElement();
626 break;
627  
628 case XmlNodeType.Text:
629 writer.WriteString(reader.Value);
630 break;
631  
632 case XmlNodeType.CDATA:
633 writer.WriteCData(reader.Value);
634 break;
635  
636 default:
637 break;
638 }
639 }
640  
641 if(m_IfStack.Count != 0)
642 {
643 throw new WarningException("Mismatched 'if', 'endif' pair");
644 }
645 }
646  
647 return xmlText.ToString();
648 }
649  
650 #endregion
651 }
652 }