1 /* Copyright 2004 The Apache Software Foundation 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * distributed under the License is distributed on an "AS IS" BASIS, 10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 * Unless required by applicable law or agreed to in writing, software 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package org.apache.xmlbeans.samples.cursor; 17 18 import org.apache.xmlbeans; 19 import org.apache.xmlbeans.samples.cursor.mixedcontent.DescriptionType; 20 import org.apache.xmlbeans.samples.cursor.mixedcontent.InventoryDocument; 21 import org.apache.xmlbeans.samples.cursor.mixedcontent.ItemType; 22 23 import java.io.File; 24 import java.io.IOException; 25 import java.util.ArrayList; 26 27 /** 28 * <p>This sample illustrates how you can use an XML cursor 29 * to manipulate the content of an element. Even though 30 * working with strongly-typed XML (in which you are accessing 31 * the XML through an API generated from schema) provides easy 32 * access for getting and setting the entire value of an 33 * element or attribute, it does not easily provide finer 34 * grained access to an element's content. This sample 35 * shows how you can use an XML cursor to "dive into" an 36 * element's content, manipulating it on a character-by- 37 * character level.</p> 38 * <p/> 39 * <p>The code in this sample is designed to look at the 40 * description of each item in an inventory list, creating 41 * a link wherever the description contains a reference 42 * to another item in the inventory list. This alters the 43 * <description> element so that it contains a mix of text and 44 * link elements. Such an element is said to have "mixed 45 * content."</p> 46 * <p/> 47 * This sample uses the schema defined in inventory.xsd. 48 */ 49 public class MixedContent 50 { 51 /** 52 * Receives an inventory XML instance and rewrites it so that items listed 53 * in the inventory point to one another via <link> elements. 54 * 55 * @param args An array containing one argument: the path to an XML instance 56 * conforming to the schema in inventory.xsd. 57 */ 58 public static void main(String[] args) 59 { 60 // Create an instance of this sample to work with. 61 MixedContent thisSample = new MixedContent(); 62 63 // Create an schema type instance from the XML indicated by the path. 64 InventoryDocument inventoryDoc = thisSample.parseXml(args[0]); 65 66 // Print what was received. 67 System.out.println("Received XML: \n\n" + inventoryDoc.toString()); 68 69 // Edit the XML, adding <link> elements to associate related items. 70 InventoryDocument linkedResultDoc = thisSample.linkItems(inventoryDoc); 71 72 // Print the updated XML. 73 System.out.println("XML with linked items: \n\n" + linkedResultDoc.toString()); 74 75 // Validate the result. 76 System.out.println("New XML is valid: " + 77 thisSample.validateXml(linkedResultDoc)); 78 } 79 80 /** 81 * <p>Creates "links" between items in an inventory list by inserting 82 * a <link> element for each linked item. An XmlCursor 83 * instance passes through each <description> element, looking 84 * for text matching the name of an item.</p> 85 * 86 * @param inventoryDoc An XML document conforming to the schema in 87 * inventory.xsd. 88 */ 89 public InventoryDocument linkItems(InventoryDocument inventoryDoc) 90 { 91 // Retrieve the <inventory> element and get an array of 92 // the <item> elements it contains. 93 InventoryDocument.Inventory inventory = inventoryDoc.getInventory(); 94 ItemType[] items = inventory.getItemArray(); 95 96 // Loop through the <item> elements, examining the 97 // description for each to see if another inventory item 98 // is mentioned. 99 for (int i = 0; i < items.length; i++) 100 { 101 // Get details about the current item, including 102 // its length. This will be used to measure text 103 // while exploring the description. 104 String itemName = items[i].getName(); 105 String itemId = new Integer(items[i].getId()).toString(); 106 int itemCharCount = itemName.length(); 107 108 // Loop through the item descriptions, looking at each 109 // for the name of the current item. 110 for (int j = 0; j < items.length; j++) 111 { 112 DescriptionType description = items[j].getDescription(); 113 114 // Insert an XmlCursor instance and set it at 115 // the beginning of the <<description> element's text, 116 // just after the start tag. 117 XmlCursor cursor = description.newCursor(); 118 cursor.toLastAttribute(); 119 cursor.toNextToken(); 120 121 // Get a String containing the characters to the 122 // immediate right of the cursor, up to the next 123 // token (in this case, the next element after 124 // the description element). Get the number of 125 // characters to the right of the cursor; this will 126 // be used to mark the distance the cursor should move 127 // before trying another item's description. Also, 128 // create a charCount variable to mark the cursor's 129 // current position. 130 String cursorChars = cursor.getChars(); 131 int descCharCount = cursorChars.length(); 132 int charCount = 0; 133 134 // As long at the cursor hasn't reached the end of the 135 // description text, check to see if the text to the 136 // cursor's immediate right matches the item name sought. 137 // If it does match, remove the text and create a link 138 // element to replace it. 139 while (charCount < descCharCount) 140 { 141 142 // A char array to hold the characters currently being 143 // checked. 144 char[] chars = new char[itemCharCount]; 145 146 // Pass the char array with the getChars method. This 147 // method will find the chars from the cursor's 148 // immediate right to the char at itemCharCount (the 149 // length of the item name currently sought). The 150 // method's second argument indicates where in the char 151 // array the found text should begin -- in this case, at the 152 // beginning. 153 int charsReturned = cursor.getChars(chars, 0, itemCharCount); 154 155 // If the characters in chars match the item name, then 156 // make a link from the text. 157 if (new String(chars).equals(itemName)) 158 { 159 // Remove the found item name. 160 cursor.removeChars(itemCharCount); 161 162 // Begin a new link element whose namespace is the 163 // same as the rest of the inventory document. The 164 // beginElement method creates a new element with the 165 // name specified around the current cursor. 166 cursor.beginElement("link", 167 "http://xmlbeans.apache.org/samples/cursor/mixedcontent"); 168 169 // Insert an id attribute and make its value the id of 170 // the item sought. 171 cursor.insertAttributeWithValue("id", itemId); 172 173 // Insert the item name as the element's value. 174 cursor.insertChars(itemName); 175 } 176 177 // Move on to the next character in the description. 178 cursor.toNextChar(1); 179 180 // Increment the counter tracking the cursor's position. 181 charCount++; 182 } 183 184 // Be sure to dispose of a cursor that's no longer needed. 185 // This allows it to be garbage collected. 186 cursor.dispose(); 187 } 188 } 189 190 // Return the edited document. 191 return inventoryDoc; 192 } 193 194 /** 195 * <p>Validates the XML, printing error messages when the XML is invalid. Note 196 * that this method will properly validate any instance of a compiled schema 197 * type because all of these types extend XmlObject.</p> 198 * 199 * <p>Note that in actual practice, you'll probably want to use an assertion 200 * when validating if you want to ensure that your code doesn't pass along 201 * invalid XML. This sample prints the generated XML whether or not it's 202 * valid so that you can see the result in both cases.</p> 203 * 204 * @param xml The XML to validate. 205 * @return <code>true</code> if the XML is valid; otherwise, <code>false</code> 206 */ 207 public boolean validateXml(XmlObject xml) 208 { 209 boolean isXmlValid = false; 210 211 // A collection instance to hold validation error messages. 212 ArrayList validationMessages = new ArrayList(); 213 214 // Validate the XML, collecting messages. 215 isXmlValid = xml.validate( 216 new XmlOptions().setErrorListener(validationMessages)); 217 218 // If the XML isn't valid, print the messages. 219 if (!isXmlValid) 220 { 221 System.out.println("Invalid XML: "); 222 for (int i = 0; i < validationMessages.size(); i++) 223 { 224 XmlError error = (XmlError) validationMessages.get(i); 225 System.out.println(error.getMessage()); 226 System.out.println(error.getObjectLocation()); 227 } 228 } 229 return isXmlValid; 230 } 231 232 /** 233 * <p>Creates a File from the XML path provided in main arguments, then 234 * parses the file's contents into a type generated from schema.</p> 235 * 236 * <p>Note that this work might have been done in main. Isolating it here 237 * makes the code separately available from outside this class.</p> 238 * 239 * @param xmlFilePath A path to XML based on the schema in inventory.xsd. 240 * @return An instance of a generated schema type that contains the parsed 241 * XML. 242 */ 243 public InventoryDocument parseXml(String xmlFilePath) 244 { 245 // Get the XML instance into a file using the path provided. 246 File inventoryFile = new File(xmlFilePath); 247 248 // Create an instance of a type generated from schema to hold the XML. 249 InventoryDocument inventoryDoc = null; 250 try 251 { 252 // Parse the instance into the type generated from the schema. 253 inventoryDoc = InventoryDocument.Factory.parse(inventoryFile); 254 } catch (XmlException e) 255 { 256 e.printStackTrace(); 257 } catch (IOException e) 258 { 259 e.printStackTrace(); 260 } 261 return inventoryDoc; 262 } 263 }