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 /* Produce a node stream describing a xaml file.
42 * This class handles the process of working out what means what in a
43 * xaml file and encodes that information into a sequence of XamlNodes
45 internal class XamlParser {
46 public const string XAML_NAMESPACE = "http://schemas.microsoft.com/winfx/xaml/2005";
47 private Mapper mapper = new Mapper(new string[] { });
48 private XmlTextReader reader;
49 private ArrayList nodeQueue = new ArrayList();
51 private enum CurrentType { Object,
55 DependencyPropertyObject,
58 private class ParserState {
60 public CurrentType type;
63 private bool begun = false;
65 private ParserState currentState(int x) {
66 if (oldStates.Count == 0) return null;
67 return (ParserState)oldStates[oldStates.Count - 1 - x];
69 private ParserState currentState() {
70 return currentState(0);
72 private ArrayList oldStates = new ArrayList();
74 int tempStateCount = 0;
75 private int getDepth() {
76 return oldStates.Count - tempStateCount;
79 public XamlParser(string filename) : this(
80 new XmlTextReader(filename))
84 public XamlParser(TextReader reader) : this(
85 new XmlTextReader(reader))
89 public XamlParser(XmlTextReader reader)
94 private XamlNode topNode()
96 return (XamlNode)nodeQueue[nodeQueue.Count - 1];
99 public XamlNode GetNextNode()
101 if (nodeQueue.Count != 0) {
102 XamlNode x = (XamlNode)nodeQueue[0];
103 nodeQueue.RemoveAt(0);
106 while (reader.Read()) {
107 Debug.WriteLine("XamlParser: NOW PARSING: " + reader.NodeType + "; " + reader.Name + "; " + reader.Value);
109 throw new XamlParseException("Too far: " + reader.NodeType + ", " + reader.Name);
110 if (currentState() != null && currentState().type == CurrentType.Code)
112 processElementInCodeState();
115 switch (reader.NodeType) {
116 case XmlNodeType.ProcessingInstruction:
119 case XmlNodeType.Element:
122 case XmlNodeType.EndElement:
125 case XmlNodeType.Text:
128 case XmlNodeType.Whitespace:
129 case XmlNodeType.Comment:
130 // skip whitespace and comments
133 throw new XamlParseException("Unknown element type " + reader.NodeType);
135 if (nodeQueue.Count != 0) {
136 XamlNode x = (XamlNode)nodeQueue[0];
137 nodeQueue.RemoveAt(0);
143 void processElementInCodeState()
145 if (reader.NodeType == XmlNodeType.EndElement &&
146 reader.LocalName == "Code" &&
147 reader.NamespaceURI == XAML_NAMESPACE) {
149 } else if (reader.NodeType != XmlNodeType.CDATA && reader.NodeType != XmlNodeType.Text) {
150 throw new XamlParseException("Code element children must be either text or CDATA nodes.");
152 currentState().obj = (string)currentState().obj + reader.Value;
159 currentState() == null &&
160 reader.NodeType != XmlNodeType.Whitespace &&
161 reader.NodeType != XmlNodeType.Comment)
169 if (reader.Name != "Mapping")
170 throw new XamlParseException("Unknown processing instruction.");
171 mapper.AddMappingProcessingInstruction(reader.Value);
176 if (reader.NamespaceURI == "")
177 throw new XamlParseException("No xml namespace specified.");
178 if (reader.LocalName == "Code" && reader.NamespaceURI == XAML_NAMESPACE) {
182 // This element must be an object if:
183 // - It's a direct child of a property element
184 // - It's a direct child of an IAddChild element
185 // and does not have a dot in its name
187 // We just check that it doesn't have a dot in it here
188 // since parseObjectElement will confirm that it is
189 // a direct child of an IAddChild.
191 // If it's a dotted name, then it is a property.
192 // What it is a property of depends on the bit of the
193 // name before the dot.
194 int dotPosition = reader.LocalName.IndexOf('.');
195 if (dotPosition < 0 ||
196 currentState().type == CurrentType.Property ||
197 currentState().type == CurrentType.DependencyProperty) {
198 parseObjectElement();
201 string beforeDot = reader.LocalName.Substring(0, dotPosition);
202 string afterDot = reader.LocalName.Substring(dotPosition + 1);
203 // If we've got this far, then currentState().Type == Object
204 if (isNameOfAncestorClass(beforeDot, (Type)currentState().obj))
205 parseNormalPropertyElement(afterDot);
207 parseDependencyPropertyElement(beforeDot, afterDot);
210 // check if the given name is the name of an ancestor of
212 bool isNameOfAncestorClass(string name, Type t)
214 if (name == "object")
216 while (t.BaseType != null) {
224 // handle an x:Code element. Most of the handling for this is
225 // at the start of the main parsing loop, in the
226 // processElementInCodeState() function
227 void parseCodeElement()
229 push(CurrentType.Code, "");
234 nodeQueue.Add(new XamlTextNode(reader.LineNumber, reader.LinePosition, getDepth(), reader.Value));
235 switch (currentState().type) {
236 case CurrentType.Object:
237 case CurrentType.PropertyObject:
238 case CurrentType.DependencyPropertyObject:
239 abortIfNotAddChild("text");
240 ((XamlTextNode)topNode()).setmode(XamlParseMode.Object);
241 // writer.CreateObjectText(reader.Value);
243 case CurrentType.DependencyProperty:
244 DependencyProperty dp = (DependencyProperty)currentState().obj;
245 // writer.CreateDependencyPropertyText(reader.Value, dp.PropertyType);
246 ((XamlTextNode)topNode()).setmode(XamlParseMode.DependencyProperty);
247 ((XamlTextNode)topNode()).setfinalType(dp.PropertyType);
249 case CurrentType.Property:
250 PropertyInfo prop = (PropertyInfo)currentState().obj;
251 // writer.CreatePropertyText(reader.Value, prop.PropertyType);
252 ((XamlTextNode)topNode()).setmode(XamlParseMode.Property);
253 ((XamlTextNode)topNode()).setfinalType(prop.PropertyType);
256 throw new NotImplementedException();
260 void abortIfNotAddChild(string thing)
262 if (!isAddChild((Type)currentState().obj))
263 throw new XamlParseException("Cannot add " + thing +
264 " to instance of '" +
265 ((Type)currentState().obj) +
269 void parseNormalPropertyElement(string propertyName)
271 // preconditions: currentState().Type == Object
272 Type currentType = (Type)currentState().obj;
273 PropertyInfo prop = currentType.GetProperty(propertyName);
276 throw new XamlParseException("Property '" + propertyName + "' not found on '" + currentType.Name + "'.");
280 // writer.CreateProperty(prop);
281 nodeQueue.Add(new XamlPropertyNode(
286 currentType.Assembly.FullName,
287 currentType.AssemblyQualifiedName,
291 BamlAttributeUsage.Default,
293 ((XamlPropertyNode)topNode()).setPropInfo(prop);
294 push(CurrentType.Property, prop);
296 if (reader.HasAttributes) {
297 throw new XamlParseException("Property node should not have attributes.");
302 void parseDependencyPropertyElement(string attachedTo, string propertyName)
304 Type currentType = (Type)currentState().obj;
305 ensureDependencyObject(currentType);
306 Type typeAttachedTo = findTypeToAttachTo(attachedTo, propertyName);
307 DependencyProperty dp = getDependencyProperty(typeAttachedTo, propertyName);
310 // writer.CreateDependencyProperty(typeAttachedTo, propertyName, dp.PropertyType);
311 nodeQueue.Add(new XamlPropertyNode(
316 currentType.Assembly.FullName,
317 currentType.AssemblyQualifiedName,
321 BamlAttributeUsage.Default,
323 ((XamlPropertyNode)topNode()).setDP(dp);
325 push(CurrentType.DependencyProperty, dp);
328 bool isAddChild(Type t)
330 return (t.GetInterface("System.Windows.Serialization.IAddChild") != null);
332 void parseObjectElement()
335 bool isEmpty = reader.IsEmptyElement;
337 parent = mapper.GetType(reader.NamespaceURI, reader.Name);
339 throw new XamlParseException("Class '" + reader.Name + "' not found.");
341 // whichever of these functions runs will push something
342 if (currentState() == null) {
343 parseTopLevelObjectElement(parent);
345 parseChildObjectElement(parent);
350 processObjectAttributes();
353 closeEmptyObjectElement();
356 void parseTopLevelObjectElement(Type parent)
358 if (reader.GetAttribute("Name", XAML_NAMESPACE) != null)
359 throw new XamlParseException("The XAML Name attribute can not be applied to top level elements\n"+
360 "Do you mean the Class attribute?");
361 if (reader.GetAttribute("Key", XAML_NAMESPACE) != null)
362 throw new XamlParseException("The XAML Key attribute can not be applied to top level elements.");
364 createTopLevel(parent.AssemblyQualifiedName, reader.GetAttribute("Class", XAML_NAMESPACE));
367 void parseChildObjectElement(Type parent)
369 if (reader.GetAttribute("Class", XAML_NAMESPACE) != null)
370 throw new XamlParseException("The XAML Class attribute can not be applied to child elements\n"+
371 "Do you mean the Name attribute?");
372 string name = reader.GetAttribute("Name", XAML_NAMESPACE);
374 name = reader.GetAttribute("Name", reader.NamespaceURI);
376 string key = reader.GetAttribute("Key", XAML_NAMESPACE);
378 Debug.WriteLine("XamlParser: parent is " + parent);
379 if (currentState().type == CurrentType.Object ||
380 currentState().type == CurrentType.PropertyObject ||
381 currentState().type == CurrentType.DependencyPropertyObject) {
382 abortIfNotAddChild("object");
383 addChild(parent, name, key);
384 } else if (currentState().type == CurrentType.Property) {
385 addPropertyChild(parent, name, key);
386 } else if (currentState().type == CurrentType.DependencyProperty) {
387 addDependencyPropertyChild(parent, name, key);
389 throw new NotImplementedException(currentState().type.ToString());
392 void processObjectAttributes()
394 if (reader.MoveToFirstAttribute()) {
396 if (reader.Name.StartsWith("xmlns"))
398 if (reader.NamespaceURI == XAML_NAMESPACE)
400 else if (reader.LocalName.IndexOf(".") < 0)
401 parseLocalPropertyAttribute();
403 parseDependencyPropertyAttribute();
404 } while (reader.MoveToNextAttribute());
408 void closeEmptyObjectElement()
410 if (currentState().type == CurrentType.Object) {
411 nodeQueue.Add(new XamlElementEndNode(
415 // writer.EndObject();
416 } else if (currentState().type == CurrentType.PropertyObject) {
417 nodeQueue.Add(new XamlElementEndNode(
421 ((XamlElementEndNode)topNode()).setpropertyObject(true);
422 ((XamlElementEndNode)topNode()).setfinalType(((PropertyInfo)currentState(1).obj).PropertyType);
423 nodeQueue.Add(new XamlPropertyComplexEndNode(
427 // ParserState state = (ParserState)oldStates[oldStates.Count - 1];
428 // writer.EndPropertyObject(((PropertyInfo)state.obj).PropertyType);
429 } else if (currentState().type == CurrentType.DependencyPropertyObject) {
430 nodeQueue.Add(new XamlElementEndNode(
434 ((XamlElementEndNode)topNode()).setdepPropertyObject(true);
435 ((XamlElementEndNode)topNode()).setfinalType(((DependencyProperty)currentState(1).obj).PropertyType);
436 nodeQueue.Add(new XamlPropertyComplexEndNode(
440 // ParserState state = (ParserState)oldStates[oldStates.Count - 1];
441 // writer.EndPropertyObject(((PropertyInfo)state.obj).PropertyType);
448 void createTopLevel(string parentName, string className)
450 Type t = Type.GetType(parentName);
451 nodeQueue.Add(new XamlDocumentStartNode(reader.LineNumber, reader.LinePosition, getDepth()));
452 nodeQueue.Add(new XamlElementStartNode(
457 t.AssemblyQualifiedName,
461 ((XamlElementStartNode)topNode()).setname(className);
463 // writer.CreateTopLevel(t, className);
464 push(CurrentType.Object, t);
467 XamlElementStartNode getChildStart(Type type, string key)
470 return new XamlElementStartNode(
474 type.Assembly.FullName,
475 type.AssemblyQualifiedName,
479 XamlKeyElementStartNode n = new XamlKeyElementStartNode(
483 type.Assembly.FullName,
484 type.AssemblyQualifiedName,
492 void addChild(Type type, string objectName, string key)
494 nodeQueue.Add(getChildStart(type, key));
495 ((XamlElementStartNode)topNode()).setname(objectName);
497 // writer.CreateObject(type, objectName);
498 push(CurrentType.Object, type);
501 void addPropertyChild(Type type, string objectName, string key)
503 // writer.CreatePropertyObject(type, objectName);
504 nodeQueue.Add(getChildStart(type, key));
505 ((XamlElementStartNode)topNode()).setname(objectName);
506 ((XamlElementStartNode)topNode()).setpropertyObject(true);
509 push(CurrentType.PropertyObject, type);
512 void addDependencyPropertyChild(Type type, string objectName, string key)
514 // writer.CreatePropertyObject(type, objectName);
515 nodeQueue.Add(getChildStart(type, key));
516 ((XamlElementStartNode)topNode()).setname(objectName);
517 ((XamlElementStartNode)topNode()).setdepPropertyObject(true);
520 push(CurrentType.DependencyPropertyObject, type);
526 void parseLocalPropertyAttribute()
528 string propertyName = reader.LocalName;
529 Type currentType = (Type)currentState().obj;
530 PropertyInfo prop = currentType.GetProperty(propertyName);
531 if (parsedAsEventProperty(currentType, propertyName))
534 throw new XamlParseException ("Property '" + propertyName + "' not found on '" + currentType.Name + "'.");
535 nodeQueue.Add(new XamlPropertyNode(
540 currentType.Assembly.FullName,
541 currentType.AssemblyQualifiedName,
545 BamlAttributeUsage.Default,
547 ((XamlPropertyNode)nodeQueue[nodeQueue.Count - 1]).setPropInfo(prop);
549 if (!prop.PropertyType.IsSubclassOf(typeof(Delegate))) {
550 nodeQueue.Add(getPropertyValueNode());
552 ((XamlTextNode)topNode()).setmode(XamlParseMode.Property);
553 // writer.CreatePropertyText(reader.Value, prop.PropertyType);
555 // writer.EndProperty();
556 ((XamlTextNode)topNode()).setfinalType(prop.PropertyType);
558 // writer.CreatePropertyDelegate(reader.Value, prop.PropertyType);
559 nodeQueue.Add(new XamlClrEventNode(
569 bool parsedAsEventProperty(Type currentType, string eventName)
571 EventInfo evt = currentType.GetEvent(eventName);
574 nodeQueue.Add(new XamlClrEventNode(
581 // writer.CreateEvent(evt);
582 // writer.CreateEventDelegate(reader.Value, evt.EventHandlerType);
583 // writer.EndEvent();
587 XamlTextNode getPropertyValueNode()
589 XamlTextNode n = new XamlTextNode(
594 if (n.TextContent.StartsWith("{StaticResource ")) {
595 n.setkeyText(n.TextContent.Remove(0, "{StaticResource ".Length).TrimEnd('}'));
602 void ensureDependencyObject(Type currentType)
604 if (!currentType.IsSubclassOf(typeof(System.Windows.DependencyObject)))
605 throw new XamlParseException("Dependency properties can only be set on "+
606 "DependencyObjects (not " + currentType.Name + ")");
608 Type findTypeToAttachTo(string attachedTo, string propertyName)
610 Type typeAttachedTo = null;
611 foreach (ParserState state in oldStates) {
612 if ((state.type == CurrentType.Object ||
613 state.type == CurrentType.PropertyObject ||
614 state.type == CurrentType.DependencyPropertyObject) &&
615 ((Type)state.obj).Name == attachedTo) {
616 typeAttachedTo = (Type)state.obj;
620 if (typeAttachedTo == null)
621 throw new XamlParseException("Nothing to attach to: " + attachedTo + "." + propertyName);
622 return typeAttachedTo;
625 DependencyProperty getDependencyProperty(Type typeAttachedTo, string propertyName)
627 FieldInfo propField = typeAttachedTo.GetField(propertyName + "Property");
628 if (propField == null)
629 throw new XamlParseException("Property '" + propertyName + "' does not exist on '" + typeAttachedTo.Name + "'.");
630 return (DependencyProperty)propField.GetValue(null);
633 void parseDependencyPropertyAttribute()
635 int index = reader.LocalName.LastIndexOf('.');
636 string attachedTo = reader.LocalName.Substring(0, index);
637 string propertyName = reader.LocalName.Substring(index + 1);
639 Type currentType = (Type)currentState().obj;
640 ensureDependencyObject(currentType);
641 Type typeAttachedTo = findTypeToAttachTo(attachedTo, propertyName);
642 DependencyProperty dp = getDependencyProperty(typeAttachedTo, propertyName);
644 nodeQueue.Add(new XamlPropertyNode(
649 currentType.Assembly.FullName,
650 currentType.AssemblyQualifiedName,
654 BamlAttributeUsage.Default,
656 ((XamlPropertyNode)topNode()).setDP(dp);
658 nodeQueue.Add(getPropertyValueNode());
659 ((XamlTextNode)topNode()).setmode(XamlParseMode.DependencyProperty);
660 ((XamlTextNode)topNode()).setfinalType(dp.PropertyType);
662 // writer.CreateDependencyProperty(typeAttachedTo, propertyName, dp.PropertyType);
663 // writer.CreateDependencyPropertyText(reader.Value, dp.PropertyType);
664 // writer.EndDependencyProperty();
667 void parseEndElement()
669 Debug.WriteLine("XamlParser: IN ENDELEMENT, SWITCHING ON " + currentState().type);
670 switch (currentState().type) {
671 case CurrentType.Code:
672 nodeQueue.Add(new XamlLiteralContentNode(
676 (string)currentState().obj));
677 // writer.CreateCode((string)currentState().obj);
679 case CurrentType.Object:
680 nodeQueue.Add(new XamlElementEndNode(
684 // writer.EndObject();
686 case CurrentType.PropertyObject:
687 nodeQueue.Add(new XamlElementEndNode(
691 ((XamlElementEndNode)topNode()).setpropertyObject(true);
692 ((XamlElementEndNode)topNode()).setfinalType(((PropertyInfo)currentState(1).obj).PropertyType);
693 nodeQueue.Add(new XamlPropertyComplexEndNode(
697 // writer.EndPropertyObject((Type)currentState().obj);
700 case CurrentType.DependencyPropertyObject:
701 nodeQueue.Add(new XamlElementEndNode(
705 ((XamlElementEndNode)topNode()).setdepPropertyObject(true);
706 ((XamlElementEndNode)topNode()).setfinalType(((DependencyProperty)currentState(1).obj).PropertyType);
707 nodeQueue.Add(new XamlPropertyComplexEndNode(
711 // writer.EndPropertyObject((Type)currentState().obj);
715 // these next two happen automatically in the new model
716 case CurrentType.Property:
717 // writer.EndProperty();
719 case CurrentType.DependencyProperty:
720 // writer.EndDependencyProperty();
728 Debug.WriteLine("XamlParser: POPPING: " + currentState().type);
729 // we are popping the last element
730 if (oldStates.Count == 1) {
732 nodeQueue.Add(new XamlDocumentEndNode(
738 int lastIndex = oldStates.Count - 1;
739 oldStates.RemoveAt(lastIndex);
741 void push(CurrentType type, Object obj)
743 Debug.WriteLine("XamlParser: PUSHING: " + oldStates.Count + " " + type);
744 ParserState currentState = new ParserState();
745 currentState.type = type;
746 currentState.obj = obj;
747 oldStates.Add(currentState);