aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Prebuild/src/Core/Parse/Preprocessor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Prebuild/src/Core/Parse/Preprocessor.cs')
-rw-r--r--Prebuild/src/Core/Parse/Preprocessor.cs652
1 files changed, 652 insertions, 0 deletions
diff --git a/Prebuild/src/Core/Parse/Preprocessor.cs b/Prebuild/src/Core/Parse/Preprocessor.cs
new file mode 100644
index 0000000..0648fad
--- /dev/null
+++ b/Prebuild/src/Core/Parse/Preprocessor.cs
@@ -0,0 +1,652 @@
1#region BSD License
2/*
3Copyright (c) 2004-2005 Matthew Holmes (matthew@wildfiregames.com), Dan Moorehead (dan05a@gmail.com)
4
5Redistribution and use in source and binary forms, with or without modification, are permitted
6provided 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
16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
17BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23*/
24#endregion
25
26using System;
27using System.Collections.Generic;
28using System.IO;
29using System.Text.RegularExpressions;
30using System.Xml;
31
32namespace 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}