At the dawn of XML, Tim Bray once said "XML is the ASCII of the future"… which is now clearly the case!
To ease reading and handling XML files, I started writing an XML browser last year. The 'Open file' menu of the app first proposed '*.xml' file extension.
As you may imagine, quite quickly that expanded to so many other file extensions: *.xml; *.xaml; *.xsd; *.wsdl; *.csproj; *.vbproj; *.dwproj; *.rptproj; *.config; *.dim; *.ds?; *.cube; *.partitions; *.manifest; *.database; *.rsd; *.rds; *.rdl the list is actually endless!
Why write a XML browser / editor?
Most XML tools I found look more like good (some excellent) editors / syntax visualizers. What I needed more was to have a logical image of the tree structure of an XML file: which node is parent of which…
Xml Note Pad did part of this but was more text-editor-oriented than logical structure visualizer. Its most recent version also dates 2007… time is running!
Of course there is not one sole way to handle such question. Visualizing the tree structure is one aspect. Editors with syntax highlighting, nodes expand/collapse… are still of course very useful.
Let us take a simple user control xaml file with the code similar to this:
Viewing the above code's node tree helps better understand its structure:
How can we handle XML tree presentation?
XML code is composed of some foundation elements:
A simple example:
<person gender="female">
|
Element = person, gender = an attribute whose value is female
|
<firstname>Anna</firstname>
|
firstname (property of person) whose value is Anna
|
<lastname>Smith</lastname>
|
lastname (property of person) whose value is Smith
|
</person>
|
End of the composite element person
|
The above code can also be written like this (1st attribute shifted to a property level):
<person>
<gender>female</gender>
<firstname>Anna</firstname>
<lastname>Smith</lastname>
</person>
You may look here for more information about the XML model.
At our .net level, we have objects defined in System.Xml.Linq that allow us to (more or less easily) explore, traverse and query a XML tree. Some of these:
- XDocument (derives from à XContainer à XNode à XObject)
- XElement (derives from à XContainer à XNode à XObject)
These xml-specialized objects are indispensable to safely communicate with the xml model, but are not good candidates for a view to represent the xml tree.
In some way, in a MMVM pattern, they represent the model (and business logic). We need an intermediate component that can be integrated into a view model and a view.
Property Bags to the rescue
I briefly mentioned property bags in a previous post about MSBuild browser. Let me elaborate a little more about this.
A property bag is a set of abstracted properties. Where a property is presented as:
- A name
- A data type
- A value
And where the property 'value' can be either:
- An object or primitive value (string for instance)
- Or a set of properties (i.e. a property bag)
This model proved quite useful in many contexts. You may for instance transform any object into a property bag representing its properties (in a strongly-typed form). This can be quite useful in situations where loose coupling is a concern (applications ßà services for instance). I will talk about this in details later in a following post.
For our current question about presenting xml trees, property bag model seems to be in sync with the xml model:
Xml element:
name / value / attributes / sub-elements (children)
|
Property bag element:
name / value / sub-elements (children)
|
To clarify by the practice, here is an example for parsing a XElement attributes into a property bag:
static PropertyBag ParseXmlNodeAttributes(XElement xnode, ObjProperty parent)
{
// create a property bag as a child of the parent property
PropertyBag targetBag = new PropertyBag(parent);
// add the xml element attributes to the bag
foreach (XAttribute attrib in xnode.Attributes())
{
targetBag.Add(attrib.Name.LocalName, attrib.Value);
}
return targetBag;
}
Parsing a XElement can be done in a few steps:
- Create an ObjProperty with name = xml element's name
- If the element's value is composite (attributes and/or child items): add element's attributes and children to the ObjProperty Children
- Otherwise set the ObjProperty value to the element's value
The following code illustrates this (simplified version of the code for clarity)
public static PropertyBag ParseXml(XElement xnode, ObjProperty parent)
{
string propertyName = xnode.Name.LocalName;
PropertyBag bagObject = new PropertyBag(propertyName);
ObjProperty property = new ObjProperty(propertyName);
PropertyBag bagAttributes = new PropertyBag();
PropertyBag bagSubItems = new PropertyBag();
// get the XEelement attributes' bag
bagAttributes = ParseXmlNodeAttributes(xnode, property);
// add attributes (if any) to Children
if (bagAttributes.Count > 0)
{
bagObject.AddRange(bagAttributes);
}
// add sub elements if any
var subNodes = xnode.Descendants();
foreach(var element in subNodes)
{
string nodeName = element.Name.LocalName;
PropertyBag elementBag = ParseXml(element, property);
ExtractPropertyValue(bagSubItems, elementBag, nodeName, property);
}
bagObject.AddRange(bagSubItems);
return bagObject;
}
The Xml Property Bags' view model
Presenting a property bag in a view requires some additional properties that are specific for a view. For instance, an 'Expanded' property for a tree node, a 'Selected' property for an item, as well as useful commands and search/filter features… etc.
Our property bag view model will embed the bag item and adds such properties.
The Xml Property Bags' Views
We need to present our xml property bags in either a tree view or a grid view.
As I said earlier, a property bag item contains either a primitive value (which may be presented as a string) or a set of child items.
In a WPF tree view that can be presented like this (code simplified for clarity):
…
<!-- import view model namespace -->
xmlns:vm="clr-namespace:iSoapExplorer2015.viewmodel"
…
<TreeView x:Name="treeView1" ItemsSource="{Binding Containers}">
<TreeView.ItemTemplate>
<!-- hierarchial data template for containers items -->
<HierarchicalDataTemplate
DataType="{x:Type vm:PropertyBagItemVM}"
ItemsSource="{Binding Containers}" > <!-- Containers = property bags -->
<StackPanel Orientation="Horizontal">
<!—note: 'Node' is the property bag item -->
<TextBlock Text="{Binding Node.Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
That results is a view that looks like the one at the beginning of this post (with an image added to the tree view item template)
A data grid view may look like this (again: code simplified for clarity):
<DataGrid ItemsSource="{Binding }">
<DataGrid.Columns>
<DataGridTextColumn Header="Property" Binding="{Binding Node.Name}" />
<DataGridTemplateColumn Header="Value" Binding="{Node.ValueAsString}" />
</DataGrid.Columns>
</DataGrid>
For a xaml Stack Panel snippet element, our view may look like this (an image column added to the above simplified code):
With some more work, you may implement more useful features and visual effects.
The current version offers: Xml nodes Tree and Grid views, Search nodes (by name and / or value), get the selected node XML, Export the entire tree to an xml file… etc.
I stopped short from editing the nodes (names / values)… but you may add such feature (before I do… probably in a near futureJ)
Here is the current version's binaries.
Have fun adding new features to the source code if you would like to.