In the last post, we showed how to create a simple XML document in which the data were entered from a DataGrid. We gave the code for constructing the XML as follows:
private XElement XmlFromLibrary() { ObservableCollection<Book> library = (ObservableCollection<Book>)((ObjectDataProvider)FindResource("LibraryGrid")).Data; XElement libraryElement = new XElement("LIBRARY", library.Select(book => new XElement("BOOK", new XElement("AUTHOR", book.Author), new XElement("TITLE", book.Title), new XElement("PRICE", book.Price)))); return libraryElement; }
The ‘library’ is fetched from a Windows resource defined in the XAML, with this resource being bound to the DataGrid (see earlier post for full details).
In writing this code, we glossed over some of the details of how the XElement is built. In fact, we used several techniques in this code that could do with further explanation.
The basic form of an XElement constructor is
XElement(XName name, params object[] content);
The first parameter gives the name of the XElement, which is used as the tag when writing out the XML. Usually, we’ll just enter a string here, and rely on the fact that the XElement constructor will convert this into an XName internally so we don’t need to worry about it.
The second parameter uses C#’s params keyword, which allows a variable number (one or more) of arguments to be passed to the constructor. As the data type of the content is just ‘object’, any data type can be passed as the content of an XElement, and it’s here that the richness of the XElement class comes into play.
There are 8 specific data types that are handled in special ways when passed in as the content.
- A string is, as you might expect, just used as is as the content of the XML tag. (In fact, a string is converted into an XText object before it is used.)
- XText: This is a special class which is added as a child node of the XElement, but its value, which is a string, is used as the XElement’s text content.
- XCData: This allows insertion of the XML CData type, which consists of unparsed character data. Such strings may contain characters such as > and &, which ordinarily have a special meaning in XML syntax, but would be ignored here.
- XElement: The content can be another XElement, which is added as a child node to the parent XElement.
- XAttribute: This object is added as a child node, and represents an attribute of the parent node.
- XComment: Allows a comment to be attached to the XElement.
- IProcessingInstruction: Allows a processing instruction to be added to the XElement. (You don’t need to worry about these for most XML that you’ll write, but I may get back to them at some point.)
- IEnumerable: This is the magic data type, since it allows collections of data, such as those produced by LINQ query operations, to be passed in as content. The elements in the collection are iterated over, and each element is treated as a separate parameter. We used this feature in the code above to insert a list of Book objects into the XML using a LINQ Select() call.
In addition, you can also pass a null as the content (which does have its uses, though we won’t go into that here).
Finally, if the content is any other data type, the XElement will call the ToString() for that data type and use that as the content. This can cause some confusion, since there are some other LINQ to XML classes (such as XDocument) that are used to attach properties to the XML file that will be accepted as content for XElement, but rather than having the expected effect, XElement will just call its ToString() method and use that as content.
As a simple example, here’s some code that creates an XElement using most of the data types above as content:
using System; using System.Xml.Linq; namespace LinqXml03 { class Program { static void Main(string[] args) { XElement document = new XElement("Library", new XComment("This is a test library"), new XElement("Program", new Program()), new XElement("Book", new XElement("Author", "Isaac Asimov"), new XElement("Title", "I, Robot"), new XAttribute("Pages", 357)), new XElement("Book", new XElement("Author", "Samuel R. Delaney"), new XElement("Title", "Nova"), new XAttribute("Pages", 293)), new XCData("This contains a > and a & character"), new XText("This also contains a > and a & character")); Console.WriteLine(document); } } }
This produces the output:
<Library> <!--This is a test library--> <Program>LinqXml03.Program</Program> <Book Pages="357"> <Author>Isaac Asimov</Author> <Title>I, Robot</Title> </Book> <Book Pages="293"> <Author>Samuel R. Delaney</Author> <Title>Nova</Title> </Book><![CDATA[This contains a > and a & character]]>This also contains a > and a & character</Library>
The top level XElement has the name ‘Library’. Its first content is a comment, which is written with the <!–…–> delimiters. Next, we’ve added a content object of type Program (that is, the class in which this program is written). The output is produced as a normal XElement tag, but the ToString() method is called from the Program class since it’s not one of the data types that has special meaning as an XElement content. The default ToString() method for a class just produces that class’s full pathname, which in this case is LinqXml03.Program.
Next, we add a couple of Book elements, each of which contains a couple of other XElements for the author and title. We’ve also added an XAttribute for the number of pages in the book.
The last two lines demonstrate the difference between XCData and XText. The XCData reproduces the given text exactly, and encloses it within the <![…]]> delimiters used for CData. The XText places the text as the content of the Library tag, and translates special characters into the XML code, so that > become > and & becomes &.
We’ve already seen an example of using IEnumerable in the code fragment at the top of this post.