001: /* Copyright 2004 The Apache Software Foundation
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * distributed under the License is distributed on an "AS IS" BASIS,
010: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: * Unless required by applicable law or agreed to in writing, software
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.apache.xmlbeans.samples.cursor;
017:
018: import org.apache.xmlbeans.*;
019: import org.apache.xmlbeans.samples.cursor.mixedcontent.DescriptionType;
020: import org.apache.xmlbeans.samples.cursor.mixedcontent.InventoryDocument;
021: import org.apache.xmlbeans.samples.cursor.mixedcontent.ItemType;
022:
023: import java.io.File;
024: import java.io.IOException;
025: import java.util.ArrayList;
026:
027: /**
028: * <p>This sample illustrates how you can use an XML cursor
029: * to manipulate the content of an element. Even though
030: * working with strongly-typed XML (in which you are accessing
031: * the XML through an API generated from schema) provides easy
032: * access for getting and setting the entire value of an
033: * element or attribute, it does not easily provide finer
034: * grained access to an element's content. This sample
035: * shows how you can use an XML cursor to "dive into" an
036: * element's content, manipulating it on a character-by-
037: * character level.</p>
038: * <p/>
039: * <p>The code in this sample is designed to look at the
040: * description of each item in an inventory list, creating
041: * a link wherever the description contains a reference
042: * to another item in the inventory list. This alters the
043: * <description> element so that it contains a mix of text and
044: * link elements. Such an element is said to have "mixed
045: * content."</p>
046: * <p/>
047: * This sample uses the schema defined in inventory.xsd.
048: */
049: public class MixedContent {
050: /**
051: * Receives an inventory XML instance and rewrites it so that items listed
052: * in the inventory point to one another via <link> elements.
053: *
054: * @param args An array containing one argument: the path to an XML instance
055: * conforming to the schema in inventory.xsd.
056: */
057: public static void main(String[] args) {
058: // Create an instance of this sample to work with.
059: MixedContent this Sample = new MixedContent();
060:
061: // Create an schema type instance from the XML indicated by the path.
062: InventoryDocument inventoryDoc = this Sample.parseXml(args[0]);
063:
064: // Print what was received.
065: System.out.println("Received XML: \n\n"
066: + inventoryDoc.toString());
067:
068: // Edit the XML, adding <link> elements to associate related items.
069: InventoryDocument linkedResultDoc = this Sample
070: .linkItems(inventoryDoc);
071:
072: // Print the updated XML.
073: System.out.println("XML with linked items: \n\n"
074: + linkedResultDoc.toString());
075:
076: // Validate the result.
077: System.out.println("New XML is valid: "
078: + this Sample.validateXml(linkedResultDoc));
079: }
080:
081: /**
082: * <p>Creates "links" between items in an inventory list by inserting
083: * a <link> element for each linked item. An XmlCursor
084: * instance passes through each <description> element, looking
085: * for text matching the name of an item.</p>
086: *
087: * @param inventoryDoc An XML document conforming to the schema in
088: * inventory.xsd.
089: */
090: public InventoryDocument linkItems(InventoryDocument inventoryDoc) {
091: // Retrieve the <inventory> element and get an array of
092: // the <item> elements it contains.
093: InventoryDocument.Inventory inventory = inventoryDoc
094: .getInventory();
095: ItemType[] items = inventory.getItemArray();
096:
097: // Loop through the <item> elements, examining the
098: // description for each to see if another inventory item
099: // is mentioned.
100: for (int i = 0; i < items.length; i++) {
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: DescriptionType description = items[j].getDescription();
112:
113: // Insert an XmlCursor instance and set it at
114: // the beginning of the <<description> element's text,
115: // just after the start tag.
116: XmlCursor cursor = description.newCursor();
117: cursor.toLastAttribute();
118: cursor.toNextToken();
119:
120: // Get a String containing the characters to the
121: // immediate right of the cursor, up to the next
122: // token (in this case, the next element after
123: // the description element). Get the number of
124: // characters to the right of the cursor; this will
125: // be used to mark the distance the cursor should move
126: // before trying another item's description. Also,
127: // create a charCount variable to mark the cursor's
128: // current position.
129: String cursorChars = cursor.getChars();
130: int descCharCount = cursorChars.length();
131: int charCount = 0;
132:
133: // As long at the cursor hasn't reached the end of the
134: // description text, check to see if the text to the
135: // cursor's immediate right matches the item name sought.
136: // If it does match, remove the text and create a link
137: // element to replace it.
138: while (charCount < descCharCount) {
139:
140: // A char array to hold the characters currently being
141: // checked.
142: char[] chars = new char[itemCharCount];
143:
144: // Pass the char array with the getChars method. This
145: // method will find the chars from the cursor's
146: // immediate right to the char at itemCharCount (the
147: // length of the item name currently sought). The
148: // method's second argument indicates where in the char
149: // array the found text should begin -- in this case, at the
150: // beginning.
151: int charsReturned = cursor.getChars(chars, 0,
152: itemCharCount);
153:
154: // If the characters in chars match the item name, then
155: // make a link from the text.
156: if (new String(chars).equals(itemName)) {
157: // Remove the found item name.
158: cursor.removeChars(itemCharCount);
159:
160: // Begin a new link element whose namespace is the
161: // same as the rest of the inventory document. The
162: // beginElement method creates a new element with the
163: // name specified around the current cursor.
164: cursor
165: .beginElement("link",
166: "http://xmlbeans.apache.org/samples/cursor/mixedcontent");
167:
168: // Insert an id attribute and make its value the id of
169: // the item sought.
170: cursor.insertAttributeWithValue("id", itemId);
171:
172: // Insert the item name as the element's value.
173: cursor.insertChars(itemName);
174: }
175:
176: // Move on to the next character in the description.
177: cursor.toNextChar(1);
178:
179: // Increment the counter tracking the cursor's position.
180: charCount++;
181: }
182:
183: // Be sure to dispose of a cursor that's no longer needed.
184: // This allows it to be garbage collected.
185: cursor.dispose();
186: }
187: }
188:
189: // Return the edited document.
190: return inventoryDoc;
191: }
192:
193: /**
194: * <p>Validates the XML, printing error messages when the XML is invalid. Note
195: * that this method will properly validate any instance of a compiled schema
196: * type because all of these types extend XmlObject.</p>
197: *
198: * <p>Note that in actual practice, you'll probably want to use an assertion
199: * when validating if you want to ensure that your code doesn't pass along
200: * invalid XML. This sample prints the generated XML whether or not it's
201: * valid so that you can see the result in both cases.</p>
202: *
203: * @param xml The XML to validate.
204: * @return <code>true</code> if the XML is valid; otherwise, <code>false</code>
205: */
206: public boolean validateXml(XmlObject xml) {
207: boolean isXmlValid = false;
208:
209: // A collection instance to hold validation error messages.
210: ArrayList validationMessages = new ArrayList();
211:
212: // Validate the XML, collecting messages.
213: isXmlValid = xml.validate(new XmlOptions()
214: .setErrorListener(validationMessages));
215:
216: // If the XML isn't valid, print the messages.
217: if (!isXmlValid) {
218: System.out.println("Invalid XML: ");
219: for (int i = 0; i < validationMessages.size(); i++) {
220: XmlError error = (XmlError) validationMessages.get(i);
221: System.out.println(error.getMessage());
222: System.out.println(error.getObjectLocation());
223: }
224: }
225: return isXmlValid;
226: }
227:
228: /**
229: * <p>Creates a File from the XML path provided in main arguments, then
230: * parses the file's contents into a type generated from schema.</p>
231: *
232: * <p>Note that this work might have been done in main. Isolating it here
233: * makes the code separately available from outside this class.</p>
234: *
235: * @param xmlFilePath A path to XML based on the schema in inventory.xsd.
236: * @return An instance of a generated schema type that contains the parsed
237: * XML.
238: */
239: public InventoryDocument parseXml(String xmlFilePath) {
240: // Get the XML instance into a file using the path provided.
241: File inventoryFile = new File(xmlFilePath);
242:
243: // Create an instance of a type generated from schema to hold the XML.
244: InventoryDocument inventoryDoc = null;
245: try {
246: // Parse the instance into the type generated from the schema.
247: inventoryDoc = InventoryDocument.Factory
248: .parse(inventoryFile);
249: } catch (XmlException e) {
250: e.printStackTrace();
251: } catch (IOException e) {
252: e.printStackTrace();
253: }
254: return inventoryDoc;
255: }
256: }
|