|
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Xml;
namespace MycroXaml.Parser
{
public interface IMycroXaml
{
void Initialize(object parent);
object ReturnedObject
{
get;
}
}
public class Parser
{
protected Hashtable nsMap;
protected Hashtable objectCollection;
protected object eventSink;
public object Load(XmlDocument doc, string objectName, object eventSink)
{
this.eventSink=eventSink;
objectCollection=new Hashtable();
object ret=null;
XmlNode node=doc.SelectSingleNode("//MycroXaml[@Name='"+objectName+"']");
Trace.Assert(node != null, "Couldn't find MycroXaml element "+objectName);
Trace.Assert(node.ChildNodes.Count==1, "Only one child of the root is allowed.");
// The last valid node instantiated is returned.
// The xml root should only have one child.
ProcessNamespaces(node);
ret=ProcessNode(node.ChildNodes[0], null);
return ret;
}
public object GetInstance(string name)
{
Trace.Assert(objectCollection.Contains(name), "The object collection does not have an entry for "+name);
return objectCollection[name];
}
public void AddInstance(string name, object obj)
{
// We don't care if we overwrite an existing object.
objectCollection[name]=obj;
}
protected void ProcessNamespaces(XmlNode node)
{
nsMap=new Hashtable();
foreach(XmlAttribute attr in node.Attributes)
{
if (attr.Prefix=="xmlns")
{
nsMap[attr.LocalName]=attr.Value;
}
}
}
protected object ProcessNode(XmlNode node, object parent)
{
object ret=null;
if (node is XmlElement)
{
// instantiate the class
string ns=node.Prefix;
string cname=node.LocalName;
Trace.Assert(nsMap.Contains(ns), "Namespace '"+ns+"' has not been declared.");
string asyName=(string)nsMap[ns];
string qname=StringHelpers.LeftOf(asyName, ',')+"."+cname+", "+StringHelpers.RightOf(asyName, ',');
Type t=Type.GetType(qname, false);
Trace.Assert(t != null, "Type "+qname+" could not be determined.");
try
{
ret=Activator.CreateInstance(t);
}
catch(Exception e)
{
Trace.Fail("Type "+qname+" could not be instantiated:\r\n"+e.Message);
}
// support the ISupportInitialize interface
if (ret is ISupportInitialize)
{
((ISupportInitialize)ret).BeginInit();
}
// If the instance implements the IMicroXaml interface, then it may need
// access to the parser.
if (ret is IMycroXaml)
{
((IMycroXaml)ret).Initialize(parent);
}
// implements the class-property-class model
ProcessChildProperties(node, ret);
string refName=ProcessAttributes(node, ret, t);
// support the ISupportInitialize interface
if (ret is ISupportInitialize)
{
((ISupportInitialize)ret).EndInit();
}
// If the instance implements the IMicroXaml interface, then it has the option
// to return an object that replaces the instance created by the parser.
if (ret is IMycroXaml)
{
ret=((IMycroXaml)ret).ReturnedObject;
if ( (ret != null) && (refName != String.Empty) )
{
AddInstance(refName, ret);
}
}
}
return ret;
}
protected void ProcessChildProperties(XmlNode node, object parent)
{
Type t=parent.GetType();
// children of a class must always be properties
foreach(XmlNode child in node.ChildNodes)
{
if (child is XmlElement)
{
string pname=child.LocalName;
PropertyInfo pi=t.GetProperty(pname);
if (pi==null)
{
// Special case--we're going to assume that the child is a class instance
// not associated with the parent object
ProcessNode(child, null);
continue;
}
// a property can only have one child node unless it's a collection
foreach(XmlNode grandChild in child.ChildNodes)
{
if (grandChild is XmlElement)
{
object propObject=pi.GetValue(parent, null);
object obj=ProcessNode(grandChild, propObject);
// A null return is valid in cases where a class implementing the IMicroXaml interface
// might want to take care of managing the instance it creates itself. See DataBinding
if (obj != null)
{
// support for ICollection objects
if (!pi.CanWrite)
{
if (propObject is ICollection)
{
MethodInfo mi=t.GetMethod("Add", new Type[] {obj.GetType()});
if (mi != null)
{
try
{
mi.Invoke(obj, new object[] {obj});
}
catch(Exception e)
{
Trace.Fail("Adding to collection failed:\r\n"+e.Message);
}
}
else if (propObject is IList)
{
try
{
((IList)propObject).Add(obj);
}
catch(Exception e)
{
Trace.Fail("List/Collection add failed:\r\n"+e.Message);
}
}
}
else
{
Trace.Fail("Unsupported read-only property: "+pname);
}
}
else
{
// direct assignment if not a collection
try
{
pi.SetValue(parent, obj, null);
}
catch(Exception e)
{
Trace.Fail("Property setter for "+pname+" failed:\r\n"+e.Message);
}
}
}
}
}
}
}
}
protected string ProcessAttributes(XmlNode node, object ret, Type t)
{
string refName=String.Empty;
// process attributes
foreach(XmlAttribute attr in node.Attributes)
{
string pname=attr.Name;
string pvalue=attr.Value;
// it's either a property or an event
PropertyInfo pi=t.GetProperty(pname);
EventInfo ei=t.GetEvent(pname);
if (pi != null)
{
// it's a property!
if ( pvalue.StartsWith("{") && pvalue.EndsWith("}") )
{
// And the value is a reference to an instance!
// Get the referenced object. Late binding is not supported!
object val=GetInstance(pvalue.Substring(1, pvalue.Length-2));
try
{
pi.SetValue(ret, val, null);
}
catch(Exception e)
{
Trace.Fail("Couldn't set property "+pname+" to an instance of "+pvalue+":\r\n"+e.Message);
}
}
else
{
// it's string, so use a type converter.
TypeConverter tc=TypeDescriptor.GetConverter(pi.PropertyType);
if (tc.CanConvertFrom(typeof(string)))
{
object val=tc.ConvertFrom(pvalue);
try
{
pi.SetValue(ret, val, null);
}
catch(Exception e)
{
Trace.Fail("Property setter for "+pname+" failed:\r\n"+e.Message);
}
}
}
// auto-add to our object collection
if (pname=="Name")
{
refName=pvalue;
AddInstance(pvalue, ret);
}
}
else if (ei != null)
{
// it's an event!
Delegate dlgt=null;
try
{
MethodInfo mi=eventSink.GetType().GetMethod(pvalue, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
dlgt=Delegate.CreateDelegate(ei.EventHandlerType, eventSink, mi.Name);
}
catch(Exception e)
{
Trace.Fail("Couldn't create a delegate for the event "+pvalue+":\r\n"+e.Message);
}
try
{
ei.AddEventHandler(ret, dlgt);
}
catch(Exception e)
{
Trace.Fail("Binding to event "+pname+" failed: "+e.Message);
}
}
else
{
// who knows what it is???
Trace.Fail("Failed acquiring property information for "+pname);
}
}
return refName;
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.