Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 57 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
57
Dung lượng
915,44 KB
Nội dung
218 CHAPTER 7 ■ THE LINQ TO XML API Listing 7-34. Creating a Text Node and Passing It As the Value of a Created Element XText xName = new XText("Joe"); XElement xFirstName = new XElement("FirstName", xName); Console.WriteLine(xFirstName); This code produces the exact same output as the previous example, and if we examine the internal state of the xFirstName object, it too is identical to the one created in the previous example: <FirstName>Joe</FirstName> Creating CData with XCData Creating an element with a CData value is also pretty simple. Listing 7-35 is an example. Listing 7-35. Creating an XCData Node and Passing It As the Value of a Created Element XElement xErrorMessage = new XElement("HTMLMessage", new XCData("<H1>Invalid user id or password.</H1>")); Console.WriteLine(xErrorMessage); This code produces the following output: <HTMLMessage><![CDATA[<H1>Invalid user id or password.</H1>]]></HTMLMessage> As you can see, the LINQ to XML API makes handling CData simple. XML Output Of course, creating, modifying, and deleting XML data does no good if you cannot persist the changes. This section contains a few ways to output your XML. Saving with XDocument.Save() You can save your XML document using any of several XDocument.Save methods. Here is a list of prototypes: void XDocument.Save(string filename); void XDocument.Save(TextWriter textWriter); void XDocument.Save(XmlWriter writer); void XDocument.Save(string filename, SaveOptions options); void XDocument.Save(TextWriter textWriter, SaveOptions options); Listing 7-36 is an example where I save the XML document to a file in my project’s folder. Rattz_789-3C07.fm Page 218 Tuesday, October 23, 2007 4:37 PM CHAPTER 7 ■ THE LINQ TO XML API 219 Listing 7-36. Saving a Document with the XDocument.Save Method XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); xDocument.Save("bookparticipants.xml"); Notice that I called the Save method on an object of type XDocument. This is because the Save methods are instance methods. The Load methods you will read about later in the “XML Input” section are static methods and must be called on the XDocument or XElement class. Here are the contents of the generated bookparticipants.xml file when viewing them in a text editor such as Notepad: <?xml version="1.0" encoding="utf-8"?> <BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> That XML document output is easy to read because the version of the Save method that I called is formatting the output. That is, if I call the version of the Save method that accepts a string filename and a SaveOptions argument, passing a value of SaveOptions.None would give the same results as the previous. Had I called the Save method like this xDocument.Save("bookparticipants.xml", SaveOptions.DisableFormatting); the results in the file would look like this: <?xml version="1.0" encoding="utf-8"?><BookParticipants><BookParticipant type= "Author" experience="first-time" language="English"><FirstName>Joe</FirstName> <LastName>Rattz</LastName></BookParticipant></BookParticipants> This is one single continuous line of text. However, you would have to examine the file in a text editor to see the difference because a browser will format it nicely for you. Of course, you can use any of the other methods available to output your document as well; it’s up to you. Saving with XElement.Save() I have said many times that with the LINQ to XML API, creating an XML document is not necessary. And to save your XML to a file, it still isn’t. The XElement class has several Save methods for this purpose: Rattz_789-3C07.fm Page 219 Tuesday, October 23, 2007 4:37 PM 220 CHAPTER 7 ■ THE LINQ TO XML API void XElement.Save(string filename); void XElement.Save(TextWriter textWriter); void XElement.Save(XmlWriter writer); void XElement.Save(string filename, SaveOptions options); void XElement.Save(TextWriter textWriter, SaveOptions options); Listing 7-37 is an example very similar to the previous, except I never even create an XML document. Listing 7-37. Saving an Element with the XElement.Save Method XElement bookParticipants = new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz"))); bookParticipants.Save("bookparticipants.xml"); And the saved XML looks identical to the previous example where I actually have an XML document: <?xml version="1.0" encoding="utf-8"?> <BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> XML Input Creating and persisting XML to a file does no good if you can’t load it back into an XML tree. Here are some techniques to read XML back in. Loading with XDocument.Load() Now that you know how to save your XML documents and fragments, you would probably like to know how to load them. You can load your XML document using any of several methods. Here is a list: static XDocument XDocument.Load(string uri); static XDocument XDocument.Load(TextReader textReader); static XDocument XDocument.Load(XmlReader reader); static XDocument XDocument.Load(string uri, LoadOptions options); static XDocument XDocument.Load(TextReader textReader, LoadOptions options); static XDocument XDocument.Load(XmlReader reader, LoadOptions options); Rattz_789-3C07.fm Page 220 Tuesday, October 23, 2007 4:37 PM CHAPTER 7 ■ THE LINQ TO XML API 221 You may notice how symmetrical these methods are to the XDocument.Save methods. However, there are a couple differences worth pointing out. First, in the Save methods, you must call the Save method on an object of XDocument or XElement type because the Save method is an instance method. But the Load method is static, so you must call it on the XDocument class itself. Second, the Save methods that accept a string are requiring filenames to be passed, whereas the Load methods that accept a string are allowing a URI to be passed. Additionally, the Load method allows a parameter of type LoadOptions to be specified while loading the XML document. The LoadOptions enum has the options shown in Table 7-2. These options can be combined with a bitwise OR (|) operation. However, some options will not work in some contexts. For example, when creating an element or a document by parsing a string, there is no line information available, nor is there a base URI. Or, when creating a document with an XmlReader, there is no base URI. Listing 7-38 shows an example where I load my XML document created in the previous example, Listing 7-37. Listing 7-38. Loading a Document with the XDocument.Load Method XDocument xDocument = XDocument.Load("bookparticipants.xml", LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); Console.WriteLine(xDocument); XElement firstName = xDocument.Descendants("FirstName").First(); Console.WriteLine("FirstName Line:{0} - Position:{1}", ((IXmlLineInfo)firstName).LineNumber, ((IXmlLineInfo)firstName).LinePosition); Console.WriteLine("FirstName Base URI:{0}", firstName.BaseUri); ■Note You must either add a using directive for System.Xml, if one is not present, or specify the namespace when referencing the IXmlLineInfo interface in your code; otherwise, the IXmlLineInfo type will not be found. Table 7-2. The LoadOptions Enumeration Option Description LoadOptions.None Use this option to specify that no load options are to be used. LoadOptions.PreserveWhitespace Use this option to preserve the whitespace in the XML source, such as blank lines. LoadOptions.SetLineInfo Use this option so that you may obtain the line and position of any object inheriting from XObject by using the IXmlLineInfo interface. LoadOptions.SetBaseUri Use this option so that you may obtain the base URI of any object inheriting from XObject. Rattz_789-3C07.fm Page 221 Tuesday, October 23, 2007 4:37 PM 222 CHAPTER 7 ■ THE LINQ TO XML API This code is loading the same XML file I created in the previous example. After I load and display the document, I obtain a reference for the FirstName element and display the line and position of the element in the source XML document. Then I display the base URI for the element. Here are the results: <BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> FirstName Line:4 - Position:6 FirstName Base URI:file:///C:/Documents and Settings/…/Projects/LINQChapter7/ LINQChapter7/bin/Debug/bookparticipants.xml This output looks just as I would expect, with one possible exception. First, the actual XML document looks fine. I see the line and position of the FirstName element, but the line number is causing me concern. It is shown as four, but in the displayed XML document, the FirstName element is on the third line. What is that about? If you examine the XML document I loaded, you will see that it begins with the document declaration, which is omitted from the output: <?xml version="1.0" encoding="utf-8"?> This is why the FirstName element is being reported as being on line four. Loading with XElement.Load() Just as you could save from either an XDocument or XElement, we can load from either as well. Loading into an element is virtually identical to loading into a document. Here are the methods available: static XElement XElement.Load(string uri); static XElement XElement.LoadTextReader textReader); static XElement XElement.Load(XmlReader reader); static XElement XElement.Load(string uri, LoadOptions options); static XElement XElement.Load(TextReader textReader, LoadOptions options); static XElement XElement.Load(XmlReader reader, LoadOptions options); These methods are static just like the XDocument.Save methods, so they must be called from the XElement class directly. Listing 7-39 contains an example loading the same XML file I saved with the XElement.Save method in Listing 7-37. Listing 7-39. Loading an Element with the XElement.Load Method XElement xElement = XElement.Load("bookparticipants.xml"); Console.WriteLine(xElement); Just as you already expect, the output looks like the following: Rattz_789-3C07.fm Page 222 Tuesday, October 23, 2007 4:37 PM CHAPTER 7 ■ THE LINQ TO XML API 223 <BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> Just as the XDocument.Load method does, the XElement.Load method has overloads that accept a LoadOptions parameter. Please see the description of these in the “Loading with XDocument.Load()” section previously in the chapter. Parsing with XDocument.Parse() or XElement.Parse() How many times have you passed XML around in your programs as a string, only to suddenly need to do some serious XML work? Getting the data from a string variable to an XML document type variable always seems like such a hassle. Well, worry yourself no longer. One of my personal favorite features of the LINQ to XML API is the parse method. Both the XDocument and XElement classes have a static method named Parse for parsing XML strings. I think by now you probably feel comfortable accepting that if you can parse with the XDocument class, you can probably parse with the XElement class, and vice-versa. And since the LINQ to XML API is all about the elements, baby, I am going to only give you an element example this time: In the “Saving with XDocument.Save” section earlier in this chapter, I show the output of the Save method if the LoadOptions parameter is specified as DisableFormatting. The result is a single string of XML. For the example in Listing 7-40, I start with that XML string (after escaping the inner quotes), parse it into an element, and output the XML element to the screen. Listing 7-40. Parsing an XML String into an Element string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><BookParticipants>" + "<BookParticipant type=\"Author\" experience=\"first-time\" language=" + "\"English\"><FirstName>Joe</FirstName><LastName>Rattz</LastName>" + "</BookParticipant></BookParticipants>"; XElement xElement = XElement.Parse(xml); Console.WriteLine(xElement); The results are the following: <BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> How cool is that? Remember the old days when you had to create a document using the W3C XML DOM XmlDocument class? Thanks to the elimination of document centricity, you can turn XML strings into real XML trees in the blink of an eye with one method call. Rattz_789-3C07.fm Page 223 Tuesday, October 23, 2007 4:37 PM 224 CHAPTER 7 ■ THE LINQ TO XML API XML Traversal XML traversal is primarily accomplished with 4 properties and 11 methods. In this section, I try to mostly use the same code example for each property or method, except I change a single argument on one line when possible. The example in Listing 7-41 builds a full XML document. Listing 7-41. A Base Example Subsequent Examples May Be Derived From // I will use this to store a reference to one of the elements in the XML tree. XElement firstParticipant; XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Notice on the next line that I am saving off a reference to the first // BookParticipant element. new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(xDocument); First, notice that I am saving a reference to the first BookParticipant element I construct. I do this so that I can have a base element from which to do all the traversal. While I will not be using the firstParticipant variable in this example, I will in the subsequent traversal examples. The next thing to notice is the argument for the Console.WriteLine method. In this case, I output the docu- ment itself. As I progress through these traversal examples, I change that argument to demonstrate how to traverse the XML tree. So here is the output showing the document from the previous example: <!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <?BookCataloger out-of-print?> <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Rattz_789-3C07.fm Page 224 Tuesday, October 23, 2007 4:37 PM CHAPTER 7 ■ THE LINQ TO XML API 225 Traversal Properties I will begin my discussion with the primary traversal properties. When directions (up, down, etc.) are specified, they are relative to the element the method is called on. In the subsequence examples, I save a reference to the first BookParticipant element, and it is the base element used for the traversal. Forward with XNode.NextNode Traversing forward through the XML tree is accomplished with the NextNode property. Listing 7-42 is an example. Listing 7-42. Traversing Forward from an XElement Object via the NextNode Property XElement firstParticipant; // A full document with all the bells and whistles. XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Notice on the next line that I am saving off a reference to the first // BookParticipant element. new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.NextNode); Since the base element is the first BookParticipant element, firstParticipant, traversing forward should provide me with the second BookParticipant element. Here are the results: <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> Based on these results I would say I am right on the money. Would you believe me if I told you that if I had accessed the PreviousNode property of the element it would have been null since it is the first node in its parent’s node list? It’s true, but I’ll leave you the task of proving it to yourself. Backward with XNode.PreviousNode If you want to traverse the XML tree backward, use the PreviousNode property. Since there is no previous node for the first participant node, I’ll get tricky and access the NextNode property first, obtaining the second participant node, as I did in the previous example, from which I will obtain the PreviousNode. If you got lost in that, I will end up back to the first participant node. That is, I will go forward with NextNode to then go backward with PreviousNode, leaving me where I started. If you have ever heard Rattz_789-3C07.fm Page 225 Tuesday, October 23, 2007 4:37 PM 226 CHAPTER 7 ■ THE LINQ TO XML API the expression “taking one step forward and taking two steps back,” with just one more access of the PreviousNode property, you could actually do that. LINQ makes it possible. Listing 7-43 is the example. Listing 7-43. Traversing Backward from an XElement Object via the PreviousNode Property XElement firstParticipant; // A full document with all the bells and whistles. XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Notice on the next line that I am saving off a reference to the first // BookParticipant element. new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.NextNode.PreviousNode); If this works as I expect, I should have the first BookParticipant element’s XML: <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> LINQ to XML actually makes traversing an XML tree fun. Up to Document with XObject.Document Obtaining the XML document from an XElement object is as simple as accessing the Document prop- erty of the element. So please notice my change to the Console.WriteLine method call, shown in Listing 7-44. Listing 7-44. Accessing the XML Document from an XElement Object via the Document Property XElement firstParticipant; // A full document with all the bells and whistles. XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Notice on the next line that I am saving off a reference to the first // BookParticipant element. new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), Rattz_789-3C07.fm Page 226 Tuesday, October 23, 2007 4:37 PM CHAPTER 7 ■ THE LINQ TO XML API 227 new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.Document); This will output the document, which is the same output as Listing 7-41, and here is the output to prove it: <!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <?BookCataloger out-of-print?> <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Up with XObject.Parent If you need to go up one level in the tree, it will probably be no surprise that the Parent property will do the job. Changing the node passed to the WriteLine method to what’s shown in Listing 7-45 changes the output (as you will see). Listing 7-45. Traversing Up from an XElement Object via the Parent Property XElement firstParticipant; // A full document with all the bells and whistles. XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Notice on the next line that I am saving off a reference to the first // BookParticipant element. new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.Parent); Rattz_789-3C07.fm Page 227 Tuesday, October 23, 2007 4:37 PM [...]... update the value of a processing instruction, simply modify the Target and Data properties of the XProcessingInstruction object Listing 7-72 is an example Listing 7-72 Updating a Processing Instruction // I will use this to store a reference for later access XProcessingInstruction procInst; XDocument xDocument = new XDocument( new XElement("BookParticipants"), procInst = new XProcessingInstruction("BookCataloger",... "out-of-print")); Console.WriteLine("Before updating processing instruction:"); Console.WriteLine(xDocument); procInst.Target = "BookParticipantContactManager"; procInst.Data = "update"; Console.WriteLine("After updating processing instruction:"); Console.WriteLine(xDocument); Now let’s take a look at the output: Before updating processing instruction: ... firstParticipant.Nodes().OfType().Single().Value = "Author of Pro LINQ: Language Integrated Query in C# 2008. "; ((XElement)firstParticipant.Element("FirstName").NextNode) Nodes().OfType().Single().Value = "Rattz, Jr."; Console.WriteLine("After updating nodes:"); Console.WriteLine(xDocument); In this example, I update the FirstName element first, using its Value property, followed by the comment using its Value property, finally... Console.WriteLine(node); } From example to example, the only thing changing will be the method called on the firstParticipant node in the foreach statement Down with XContainer.Nodes() No, I am not expressing my disdain for nodes Nor am I stating I am all in favor of nodes, as in being “down for” rock climbing, meaning being excited about the prospect of going rock climbing I am merely describing the direction... After updating document type: 251 Rattz_789-3C07.fm Page 252 Tuesday, October 23, 2007 4:37 PM 252 CHAPTER 7 ■ THE LINQ TO XML API XProcessingInstruction.Target on XProcessingInstruction Objects and XProcessingInstruction.Data on XProcessingInstruction... "Buckingham")))); foreach (XNode node in firstParticipant.Elements("FirstName")) { Console.WriteLine(node); } This code produces the following: Joe Down with XContainer.Element() You may obtain the first child element matching a specified name using the Element method Instead of a sequence being returned requiring a foreach loop, I will have a single element returned, as shown in. .. of Pro LINQ: Language Integrated Query in C# 2008. > Joey Rattz, Jr. As you can see, all of the node’s values are updated XDocumentType.Name, XDocumentType.PublicId, XDocumentType.SystemId, and XDocumentType.InternalSubset on XDocumentType Objects To update a document type node, the XDocumentType class provides four properties... XElement("LastName", "Buckingham")))); foreach (XNode node in firstParticipant.Nodes().OfType()) { Console.WriteLine(node); } As you can see, the XComment and XProcessingInstruction objects are still being created But since I am now calling the OfType operator, the code produces these results: Joe Rattz Are you starting to see how cleverly all the new C# language. .. XElement("LastName", "Buckingham")))); foreach (XNode node in firstParticipant.Nodes()) { Console.WriteLine(node); } 229 Rattz_789-3C07.fm Page 230 Tuesday, October 23, 2007 4:37 PM 230 CHAPTER 7 ■ THE LINQ TO XML API This example is different than the previous in that there is now a comment and processing instruction added to the first BookParticipant element Pressing Ctrl+F5 displays the following: In the example code, since the initial reference into the document is the first BookParticipant node, I obtain a reference to the second BookParticipant node using the NextNode property of the first BookParticipant node so that there are more nodes to return, as shown in Listing 7-60 Listing 7-60 Traversing Backward from the Current . whitespace in the XML source, such as blank lines. LoadOptions.SetLineInfo Use this option so that you may obtain the line and position of any object inheriting from XObject by using the IXmlLineInfo. Nor am I stating I am all in favor of nodes, as in being “down for” rock climbing, meaning being excited about the prospect of going rock climbing. I am merely describing the direction of traversal. the Parent property will do the job. Changing the node passed to the WriteLine method to what’s shown in Listing 7- 45 changes the output (as you will see). Listing 7- 45. Traversing Up from