Monday, September 03, 2007

Working with XML in Silverlight and bonus - generic Property Bag implementation

I believe, that if you are in programming, you know what Property Bag is. This is handy generic class, that can "eat" poor-known structure and process a list with all this information. You often can access properties of the parent class by name.

Why this good? Property Bag provides very intuitive way to access data. This type safe collection, that has not single type, but can be filled with data of different types. Let's see an example of such data.

 

<vCard>

<
FullName>

<
FamilyName>Crystal</FamilyName>

<
GivenName>Corky</GivenName>

</
FullName>

<
Title>Computer Officer Class 3</Title>

<
Role>Programmer</Role>

<
Phone>+61 7 555 5555</Phone>

<
Email>corky@qqqfoo.com</Email>

<
Organization>

<
OrganizationName>qqqfoo.com Pty Ltd</OrganizationName>

<
OrganizationUnit> Commercialisation Division </OrganizationUnit>

</
Organization>

<
Address>

<
Street>111 Lake Drive</Street>

<
Locality>WonderCity </Locality>

<
Pcode>55555</Pcode>

<
Country>Australia</Country>

</
Address>

</
vCard>


 






Well, in order to get any information from such file, you need to parse XML and know it's structure or DTD. If someone will change the file and remove FullName tag, for example, all your program often have to be rewritten. So, we're implementing PropertyBag helper call to take care on such cases. Here the simple implementation of such class in C#






 

public class PropertyBag : List<KeyValuePair<string, string>>

{


public PropertyBag (XmlDocument source)

{


//((WaitCallback)delegate

//{

fillTheBag(source.ChildNodes);

//}).BeginInvoke(null, null, null);

}




void fillTheBag(XmlNodeList root)

{


for (int i = 0; i < root.Count; i++)

{


if (root[i].ChildNodes.Count > 0)

{


fillTheBag(root[i].ChildNodes);


}


else

{

base.Add(new KeyValuePair<string, string>(root[i].ParentNode.Name, root[i].InnerText));

}


}


}





public string GetPropertyValue(string property)

{


for (int i = 0; i < base.Count; i++)

{


if (base[i].Key == property)

return base[i].Value;

}


return string.Empty;

}


}


 






Now, you can create PropertyBag by using easy constructor with XmlDocument as parameter and call any of internal information, found in source XML as bag["FirstName"].



This is very cool, but when we want to implement such approach in Silverlight, we will find, that there is no XmlDocument there, as well as no DOM at all. The only thing, you can use is XmlReader, that reads information from string stream and parses in on the fly. So how to know, when we have nested elements, how to know, that we have types of data. Let me introduce you SilverPropertyBag, useful class to read information from remote XML file and put it into property bag.






 

public class SilverPropertiesBag : List<KeyValuePair<string, string>>

{


public SilverPropertiesBag(string xmlPath)

{


XmlReaderSettings settings = new XmlReaderSettings();

settings.ConformanceLevel =
ConformanceLevel.Fragment;

settings.IgnoreWhitespace =
true;

settings.IgnoreComments =
true;

settings.IgnoreProcessingInstructions =
true;




try

{

HttpWebRequest request = new BrowserHttpWebRequest(new Uri(xmlPath));

HttpWebResponse response = request.GetResponse();

Stream content = response.GetResponseStream();




XmlReader reader = XmlReader.Create(content, settings);

reader.MoveToFirstAttribute();





fillTheBag(reader);


}


catch {}

}





void fillTheBag(XmlReader root)

{


int depth = root.Depth;

KeyValuePair<string, string> last = new KeyValuePair<string, string>();

while (root.Read())

{


switch (root.NodeType)

{


case XmlNodeType.Text:

last =
new KeyValuePair<string, string>(root.Name, root.Value);

break;

case XmlNodeType.EndElement:

if (last.Key == string.Empty && last.Value != string.Empty)

{


last =
new KeyValuePair<string, string>(root.Name, last.Value);

base.Add(last);

last =
new KeyValuePair<string,string>();

}


if (depth > root.Depth)

return;

break;




}


if (root.IsEmptyElement || root.EOF) return;

else if (root.IsStartElement())

{


string name = root.Name;




if (root.HasAttributes)

{


while (root.MoveToNextAttribute())

{


base.Add(new KeyValuePair<string, string>(root.Name, root.Value));

}


root.Read();


if (root.NodeType == XmlNodeType.CDATA && root.Value.Length == 0)

{


root.Read();


}





if (root.NodeType == XmlNodeType.Text && root.ValueType == typeof(string) && root.Value != string.Empty)

{


base.Add(new KeyValuePair<string, string>(name, root.Value));

}





if (root.NodeType == XmlNodeType.EndElement && root.Name.Equals(name))

{


root.Read();


if (root.Depth < depth)

return;

else

continue;




}





if (root.IsStartElement())

{





if (root.Depth > depth)

fillTheBag(root);


else

continue;

}


root.Read();


}


}


}


}





public string GetPropertyValue(string property)

{


for (int i = 0; i < base.Count; i++)

{


if (base[i].Key == property)

return base[i].Value;

}


return string.Empty;

}





}


 






Well. It's pretty easy to understand how it works, but it's extremely useful for  any of your Silverlight projects. Happy programming.

No comments: