5 // Iain McCoy (iain@mccoy.id.au)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
32 using System.Diagnostics;
35 using System.Reflection;
37 using System.Windows.Serialization;
39 namespace Mono.Windows.Serialization {
40 public class XamlParser {
41 public const string XAML_NAMESPACE = "http://schemas.microsoft.com/winfx/xaml/2005";
42 private Mapper mapper = new Mapper(new string[] { });
43 private XmlTextReader reader;
44 private ArrayList nodeQueue = new ArrayList();
46 private enum CurrentType { Object,
52 private class ParserState {
54 public CurrentType type;
57 private bool begun = false;
59 private ParserState currentState() {
60 if (oldStates.Count == 0) return null;
61 return (ParserState)oldStates[oldStates.Count - 1];
63 private ArrayList oldStates = new ArrayList();
65 int tempStateCount = 0;
66 private int getDepth() {
67 return oldStates.Count - tempStateCount;
70 public XamlParser(string filename) : this(
71 new XmlTextReader(filename))
75 public XamlParser(TextReader reader) : this(
76 new XmlTextReader(reader))
80 public XamlParser(XmlTextReader reader)
85 private XamlNode topNode()
87 return (XamlNode)nodeQueue[nodeQueue.Count - 1];
90 public XamlNode GetNextNode()
92 if (nodeQueue.Count != 0) {
93 XamlNode x = (XamlNode)nodeQueue[0];
94 nodeQueue.RemoveAt(0);
97 while (reader.Read()) {
98 Debug.WriteLine("XamlParser: NOW PARSING: " + reader.NodeType + "; " + reader.Name + "; " + reader.Value);
100 throw new Exception("Too far: " + reader.NodeType + ", " + reader.Name);
101 if (currentState() != null && currentState().type == CurrentType.Code)
103 processElementInCodeState();
106 switch (reader.NodeType) {
107 case XmlNodeType.ProcessingInstruction:
110 case XmlNodeType.Element:
113 case XmlNodeType.EndElement:
116 case XmlNodeType.Text:
119 case XmlNodeType.Whitespace:
120 case XmlNodeType.Comment:
121 // skip whitespace and comments
124 throw new Exception("Unknown element type " + reader.NodeType);
126 if (nodeQueue.Count != 0) {
127 XamlNode x = (XamlNode)nodeQueue[0];
128 nodeQueue.RemoveAt(0);
134 void processElementInCodeState()
136 if (reader.NodeType == XmlNodeType.EndElement &&
137 reader.LocalName == "Code" &&
138 reader.NamespaceURI == XAML_NAMESPACE) {
140 } else if (reader.NodeType != XmlNodeType.CDATA && reader.NodeType != XmlNodeType.Text) {
141 throw new Exception("Code element children must be either text or CDATA nodes.");
143 currentState().obj = (string)currentState().obj + reader.Value;
150 currentState() == null &&
151 reader.NodeType != XmlNodeType.Whitespace &&
152 reader.NodeType != XmlNodeType.Comment)
160 if (reader.Name != "Mapping")
161 throw new Exception("Unknown processing instruction.");
162 mapper.AddMappingProcessingInstruction(reader.Value);
167 if (reader.NamespaceURI == "")
168 throw new Exception("No xml namespace specified.");
169 if (reader.LocalName == "Code" && reader.NamespaceURI == XAML_NAMESPACE) {
173 // This element must be an object if:
174 // - It's a direct child of a property element
175 // - It's a direct child of an IAddChild element
176 // and does not have a dot in its name
178 // We just check that it doesn't have a dot in it here
179 // since parseObjectElement will confirm that it is
180 // a direct child of an IAddChild.
182 // If it's a dotted name, then it is a property.
183 // What it is a property of depends on the bit of the
184 // name before the dot.
185 int dotPosition = reader.LocalName.IndexOf('.');
186 if (dotPosition < 0 ||
187 currentState().type == CurrentType.Property) {
188 parseObjectElement();
191 string beforeDot = reader.LocalName.Substring(0, dotPosition);
192 string afterDot = reader.LocalName.Substring(dotPosition + 1);
193 // If we've got this far, then currentState().Type == Object
194 if (isNameOfAncestorClass(beforeDot, (Type)currentState().obj))
195 parseNormalPropertyElement(afterDot);
197 parseDependencyPropertyElement(beforeDot, afterDot);
200 // check if the given name is the name of an ancestor of
202 bool isNameOfAncestorClass(string name, Type t)
204 if (name == "object")
206 while (t.BaseType != null) {
214 // handle an x:Code element. Most of the handling for this is
215 // at the start of the main parsing loop, in the
216 // processElementInCodeState() function
217 void parseCodeElement()
219 push(CurrentType.Code, "");
224 nodeQueue.Add(new XamlTextNode(reader.LineNumber, reader.LinePosition, getDepth(), reader.Value));
225 switch (currentState().type) {
226 case CurrentType.Object:
227 case CurrentType.PropertyObject:
228 abortIfNotAddChild("text");
229 ((XamlTextNode)topNode()).setmode(XamlParseMode.Object);
230 // writer.CreateObjectText(reader.Value);
232 case CurrentType.DependencyProperty:
233 DependencyProperty dp = (DependencyProperty)currentState().obj;
234 // writer.CreateDependencyPropertyText(reader.Value, dp.PropertyType);
235 ((XamlTextNode)topNode()).setmode(XamlParseMode.DependencyProperty);
236 ((XamlTextNode)topNode()).setfinalType(dp.PropertyType);
238 case CurrentType.Property:
239 PropertyInfo prop = (PropertyInfo)currentState().obj;
240 // writer.CreatePropertyText(reader.Value, prop.PropertyType);
241 ((XamlTextNode)topNode()).setmode(XamlParseMode.Property);
242 ((XamlTextNode)topNode()).setfinalType(prop.PropertyType);
245 throw new NotImplementedException();
249 void abortIfNotAddChild(string thing)
251 if (!isAddChild((Type)currentState().obj))
252 throw new Exception("Cannot add " + thing +
253 " to instance of '" +
254 ((Type)currentState().obj) +
258 void parseNormalPropertyElement(string propertyName)
260 // preconditions: currentState().Type == Object
261 Type currentType = (Type)currentState().obj;
262 PropertyInfo prop = currentType.GetProperty(propertyName);
265 throw new Exception("Property '" + propertyName + "' not found on '" + currentType.Name + "'.");
269 // writer.CreateProperty(prop);
270 nodeQueue.Add(new XamlPropertyNode(
275 currentType.Assembly.FullName,
276 currentType.AssemblyQualifiedName,
280 BamlAttributeUsage.Default,
282 ((XamlPropertyNode)topNode()).setPropInfo(prop);
283 push(CurrentType.Property, prop);
285 if (reader.HasAttributes) {
286 throw new Exception("Property node should not have attributes.");
291 void parseDependencyPropertyElement(string attachedTo, string propertyName)
293 Type currentType = (Type)currentState().obj;
294 ensureDependencyObject(currentType);
295 Type typeAttachedTo = findTypeToAttachTo(attachedTo, propertyName);
296 DependencyProperty dp = getDependencyProperty(typeAttachedTo, propertyName);
299 // writer.CreateDependencyProperty(typeAttachedTo, propertyName, dp.PropertyType);
300 nodeQueue.Add(new XamlPropertyNode(
305 currentType.Assembly.FullName,
306 currentType.AssemblyQualifiedName,
310 BamlAttributeUsage.Default,
312 ((XamlPropertyNode)topNode()).setDP(dp);
314 push(CurrentType.DependencyProperty, dp);
317 bool isAddChild(Type t)
319 return (t.GetInterface("System.Windows.Serialization.IAddChild") != null);
321 void parseObjectElement()
324 bool isEmpty = reader.IsEmptyElement;
326 parent = mapper.GetType(reader.NamespaceURI, reader.Name);
328 throw new Exception("Class '" + reader.Name + "' not found.");
330 // whichever of these functions runs will push something
331 if (currentState() == null) {
332 parseTopLevelObjectElement(parent);
334 parseChildObjectElement(parent);
339 processObjectAttributes();
342 closeEmptyObjectElement();
345 void parseTopLevelObjectElement(Type parent)
347 if (reader.GetAttribute("Name", XAML_NAMESPACE) != null)
348 throw new Exception("The XAML Name attribute can not be applied to top level elements\n"+
349 "Do you mean the Class attribute?");
351 createTopLevel(parent.AssemblyQualifiedName, reader.GetAttribute("Class", XAML_NAMESPACE));
354 void parseChildObjectElement(Type parent)
356 if (reader.GetAttribute("Class", XAML_NAMESPACE) != null)
357 throw new Exception("The XAML Class attribute can not be applied to child elements\n"+
358 "Do you mean the Name attribute?");
359 string name = reader.GetAttribute("Name", XAML_NAMESPACE);
361 name = reader.GetAttribute("Name", reader.NamespaceURI);
363 Debug.WriteLine("XamlParser: parent is " + parent);
364 if (currentState().type == CurrentType.Object) {
365 abortIfNotAddChild("object");
366 addChild(parent, name);
367 } else if (currentState().type == CurrentType.Property) {
368 addPropertyChild(parent, name);
370 throw new NotImplementedException();
373 void processObjectAttributes()
375 if (reader.MoveToFirstAttribute()) {
377 if (reader.Name.StartsWith("xmlns"))
379 if (reader.NamespaceURI == XAML_NAMESPACE)
381 if (reader.LocalName.IndexOf(".") < 0)
382 parseLocalPropertyAttribute();
384 parseDependencyPropertyAttribute();
385 } while (reader.MoveToNextAttribute());
389 void closeEmptyObjectElement()
391 if (currentState().type == CurrentType.Object) {
392 nodeQueue.Add(new XamlElementEndNode(
396 // writer.EndObject();
397 } else if (currentState().type == CurrentType.PropertyObject) {
398 nodeQueue.Add(new XamlElementEndNode(
402 ((XamlElementEndNode)topNode()).setpropertyObject(true);
403 nodeQueue.Add(new XamlPropertyComplexEndNode(
407 ((XamlPropertyComplexEndNode)topNode()).setfinalType((Type)currentState().obj);
408 // ParserState state = (ParserState)oldStates[oldStates.Count - 1];
409 // writer.EndPropertyObject(((PropertyInfo)state.obj).PropertyType);
415 void createTopLevel(string parentName, string className)
417 Type t = Type.GetType(parentName);
418 nodeQueue.Add(new XamlDocumentStartNode(reader.LineNumber, reader.LinePosition, getDepth()));
419 nodeQueue.Add(new XamlElementStartNode(
424 t.AssemblyQualifiedName,
428 ((XamlElementStartNode)topNode()).setname(className);
430 // writer.CreateTopLevel(t, className);
431 push(CurrentType.Object, t);
434 void addChild(Type type, string objectName)
436 nodeQueue.Add(new XamlElementStartNode(
440 type.Assembly.FullName,
441 type.AssemblyQualifiedName,
444 ((XamlElementStartNode)topNode()).setname(objectName);
446 // writer.CreateObject(type, objectName);
447 push(CurrentType.Object, type);
450 void addPropertyChild(Type type, string objectName)
452 // writer.CreatePropertyObject(type, objectName);
453 nodeQueue.Add(new XamlElementStartNode(
457 type.Assembly.FullName,
458 type.AssemblyQualifiedName,
461 ((XamlElementStartNode)topNode()).setname(objectName);
462 ((XamlElementStartNode)topNode()).setpropertyObject(true);
465 push(CurrentType.PropertyObject, type);
470 void parseLocalPropertyAttribute()
472 string propertyName = reader.LocalName;
473 Type currentType = (Type)currentState().obj;
474 PropertyInfo prop = currentType.GetProperty(propertyName);
475 if (parsedAsEventProperty(currentType, propertyName))
478 throw new Exception ("Property '" + propertyName + "' not found on '" + currentType.Name + "'.");
479 nodeQueue.Add(new XamlPropertyNode(
484 currentType.Assembly.FullName,
485 currentType.AssemblyQualifiedName,
489 BamlAttributeUsage.Default,
491 ((XamlPropertyNode)nodeQueue[nodeQueue.Count - 1]).setPropInfo(prop);
493 if (!prop.PropertyType.IsSubclassOf(typeof(Delegate))) {
495 nodeQueue.Add(new XamlTextNode(
501 ((XamlTextNode)topNode()).setmode(XamlParseMode.Property);
502 // writer.CreatePropertyText(reader.Value, prop.PropertyType);
504 // writer.EndProperty();
505 ((XamlTextNode)topNode()).setfinalType(prop.PropertyType);
507 // writer.CreatePropertyDelegate(reader.Value, prop.PropertyType);
508 nodeQueue.Add(new XamlClrEventNode(
518 bool parsedAsEventProperty(Type currentType, string eventName)
520 EventInfo evt = currentType.GetEvent(eventName);
523 nodeQueue.Add(new XamlClrEventNode(
530 // writer.CreateEvent(evt);
531 // writer.CreateEventDelegate(reader.Value, evt.EventHandlerType);
532 // writer.EndEvent();
536 void ensureDependencyObject(Type currentType)
538 if (!currentType.IsSubclassOf(typeof(System.Windows.DependencyObject)))
539 throw new Exception("Dependency properties can only be set on "+
540 "DependencyObjects (not " + currentType.Name + ")");
542 Type findTypeToAttachTo(string attachedTo, string propertyName)
544 Type typeAttachedTo = null;
545 foreach (ParserState state in oldStates) {
546 if ((state.type == CurrentType.Object ||
547 state.type == CurrentType.PropertyObject) &&
548 ((Type)state.obj).Name == attachedTo) {
549 typeAttachedTo = (Type)state.obj;
553 if (typeAttachedTo == null)
554 throw new Exception("Nothing to attach to: " + attachedTo + "." + propertyName);
555 return typeAttachedTo;
558 DependencyProperty getDependencyProperty(Type typeAttachedTo, string propertyName)
560 FieldInfo propField = typeAttachedTo.GetField(propertyName + "Property");
561 if (propField == null)
562 throw new Exception("Property '" + propertyName + "' does not exist on '" + typeAttachedTo.Name + "'.");
563 return (DependencyProperty)propField.GetValue(null);
566 void parseDependencyPropertyAttribute()
568 int index = reader.LocalName.LastIndexOf('.');
569 string attachedTo = reader.LocalName.Substring(0, index);
570 string propertyName = reader.LocalName.Substring(index + 1);
572 Type currentType = (Type)currentState().obj;
573 ensureDependencyObject(currentType);
574 Type typeAttachedTo = findTypeToAttachTo(attachedTo, propertyName);
575 DependencyProperty dp = getDependencyProperty(typeAttachedTo, propertyName);
577 nodeQueue.Add(new XamlPropertyNode(
582 currentType.Assembly.FullName,
583 currentType.AssemblyQualifiedName,
587 BamlAttributeUsage.Default,
589 ((XamlPropertyNode)topNode()).setDP(dp);
591 nodeQueue.Add(new XamlTextNode(reader.LineNumber, reader.LinePosition, getDepth(), reader.Value));
592 ((XamlTextNode)topNode()).setmode(XamlParseMode.DependencyProperty);
593 ((XamlTextNode)topNode()).setfinalType(dp.PropertyType);
595 // writer.CreateDependencyProperty(typeAttachedTo, propertyName, dp.PropertyType);
596 // writer.CreateDependencyPropertyText(reader.Value, dp.PropertyType);
597 // writer.EndDependencyProperty();
600 void parseEndElement()
602 Debug.WriteLine("XamlParser: IN ENDELEMENT, SWITCHING ON " + currentState().type);
603 switch (currentState().type) {
604 case CurrentType.Code:
605 nodeQueue.Add(new XamlLiteralContentNode(
609 (string)currentState().obj));
610 // writer.CreateCode((string)currentState().obj);
612 case CurrentType.Object:
613 nodeQueue.Add(new XamlElementEndNode(
617 // writer.EndObject();
619 case CurrentType.PropertyObject:
620 nodeQueue.Add(new XamlElementEndNode(
624 ((XamlElementEndNode)topNode()).setpropertyObject(true);
625 nodeQueue.Add(new XamlPropertyComplexEndNode(
629 Debug.WriteLine("XamlParser: XXXXXXXX" + currentState().obj);
630 Debug.WriteLine("XamlParser: XXXXXXXX" + (currentState().obj is Type));
631 ((XamlPropertyComplexEndNode)topNode()).setfinalType((Type)currentState().obj);
632 Debug.WriteLine("XamlParser: XXXXXXXX" + ((XamlPropertyComplexEndNode)topNode()).finalType);
633 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 1]).obj.GetType());
634 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 1]).type);
635 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 2]).obj.GetType());
636 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 2]).type);
637 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 3]).obj.GetType());
638 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 3]).type);
639 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 4]).obj.GetType());
640 Debug.WriteLine("TTTTTTTTT " + ((ParserState)oldStates[oldStates.Count - 4]).type);
641 // writer.EndPropertyObject((Type)currentState().obj);
644 // these next two happen automatically in the new model
645 case CurrentType.Property:
646 // writer.EndProperty();
648 case CurrentType.DependencyProperty:
649 // writer.EndDependencyProperty();
657 Debug.WriteLine("XamlParser: POPPING: " + currentState().type);
658 // we are popping the last element
659 if (oldStates.Count == 1) {
661 nodeQueue.Add(new XamlDocumentEndNode(
667 int lastIndex = oldStates.Count - 1;
668 oldStates.RemoveAt(lastIndex);
670 void push(CurrentType type, Object obj)
672 Debug.WriteLine("XamlParser: PUSHING: " + oldStates.Count + " " + type);
673 ParserState currentState = new ParserState();
674 currentState.type = type;
675 currentState.obj = obj;
676 oldStates.Add(currentState);