1 /* Copyright 2009 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 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 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 java.io.File; 19 import java.io.IOException; 20 import java.util.Comparator; 21 22 import org.apache.xmlbeans.XmlCursor; 23 import org.apache.xmlbeans.XmlException; 24 import org.apache.xmlbeans.XmlObject; 25 26 import javax.xml.namespace.QName; 27 28 /** 29 */ 30 public final class XmlSort 31 { 32 /** 33 * Receives an XML element instance and sorts the children of this 34 * element in lexicographical (by default) order. 35 * 36 * @param args An array in which the first item is a 37 * path to the XML instance file and the second item (optional) is 38 * an XPath inside the document identifying the element to be sorted 39 */ 40 public static void main(String[] args) 41 { 42 if (args.length < 1 || args.length > 2) 43 { 44 System.out.println(" java XmlSort <XML_File> [<XPath>]"); 45 return; 46 } 47 File f = new File(args[0]); 48 try 49 { 50 XmlObject docInstance = XmlObject.Factory.parse(f); 51 XmlObject element = null; 52 if (args.length > 1) 53 { 54 String xpath = args[1]; 55 XmlObject[] result = docInstance.selectPath(xpath); 56 if (result.length == 0) 57 { 58 System.out.println("ERROR: XPath \"" + xpath + "\" did not return any results"); 59 } 60 else if (result.length > 1) 61 { 62 System.out.println("ERROR: XPath \"" + xpath + "\" returned more than one " + 63 "node (" + result.length + ")"); 64 } 65 else 66 element = result[0]; 67 } 68 else 69 { 70 // Navigate to the root element 71 XmlCursor c = docInstance.newCursor(); 72 c.toFirstChild(); 73 element = c.getObject(); 74 c.dispose(); 75 } 76 if (element != null) 77 sort(element, new QNameComparator(QNameComparator.ASCENDING)); 78 System.out.println(docInstance.xmlText()); 79 } 80 catch (IOException ioe) 81 { 82 System.out.println("ERROR: Could not open file: \"" + args[0] + "\": " + 83 ioe.getMessage()); 84 } 85 catch (XmlException xe) 86 { 87 System.out.println("ERROR: Could not parse file: \"" + args[0] + "\": " + 88 xe.getMessage()); 89 } 90 } 91 92 /** 93 * Sorts the children of <code>element</code> according to the order indicated by the 94 * comparator. 95 * @param element the element whose content is to be sorted. Only element children are sorted, 96 * attributes are not touched. When elements are reordered, all the text, comments and PIs 97 * follow the element that they come immediately after. 98 * @param comp a comparator that is to be used when comparing the <code>QName</code>s of two 99 * elements. See {@link org.apache.xmlbeans.samples.cursor.XmlSort.QNameComparator} for a simple 100 * implementation that compares two elements based on the value of their QName, but more 101 * complicated implementations are possible, for instance, ones that compare two elements based 102 * on the value of a specifc attribute etc. 103 * @throws IllegalArgumentException if the input <code>XmlObject</code> does not represent 104 * an element 105 */ 106 public static void sort(XmlObject element, Comparator comp) 107 { 108 XmlCursor headCursor = element.newCursor(); 109 if (!headCursor.isStart()) 110 throw new IllegalStateException("The element parameter must point to a STARTDOC"); 111 // We use insertion sort to minimize the number of swaps, because each swap means 112 // moving a part of the document 113 /* headCursor points to the beginning of the list of the already sorted items and 114 listCursor points to the beginning of the list of unsorted items 115 At the beginning, headCursor points to the first element and listCursor points to the 116 second element. The algorithm ends when listCursor cannot be moved to the "next" 117 element in the unsorted list, i.e. the unsorted list becomes empty */ 118 boolean moved = headCursor.toFirstChild(); 119 if (!moved) 120 { 121 // Cursor was not moved, which means that the given element has no children and 122 // therefore there is nothing to sort 123 return; 124 } 125 XmlCursor listCursor = headCursor.newCursor(); 126 boolean moreElements = listCursor.toNextSibling(); 127 while (moreElements) 128 { 129 moved = false; 130 // While we can move the head of the unsorted list, it means that there are still 131 // items (elements) that need to be sorted 132 while (headCursor.comparePosition(listCursor) < 0) 133 { 134 if (comp.compare(headCursor, listCursor) > 0) 135 { 136 // We have found the position in the sorted list, insert the element and the 137 // text following the element in the current position 138 /* 139 * Uncomment this code to cause the text before the element to move along 140 * with the element, rather than the text after the element. Notice that this 141 * is more difficult to do, because the cursor's "type" refers to the position 142 * to the right of the cursor, so to get the type of the token to the left, the 143 * cursor needs to be first moved to the left (previous token) 144 * 145 headCursor.toPrevToken(); 146 while (headCursor.isComment() || headCursor.isProcinst() || headCursor.isText()) 147 headCursor.toPrevToken(); 148 headCursor.toNextToken(); 149 listCursor.toPrevToken(); 150 while (listCursor.isComment() || listCursor.isProcinst() || listCursor.isText()) 151 listCursor.toPrevToken(); 152 listCursor.toNextToken(); 153 while (!listCursor.isStart()) 154 listCursor.moveXml(headCursor); 155 listCursor.moveXml(headCursor); 156 */ 157 // Move the element 158 listCursor.moveXml(headCursor); 159 // Move the text following the element 160 while (!listCursor.isStart() && !listCursor.isEnd()) 161 listCursor.moveXml(headCursor); 162 moreElements = listCursor.isStart(); 163 moved = true; 164 break; 165 } 166 headCursor.toNextSibling(); 167 } 168 if (!moved) 169 { 170 // Because during the move of a fragment of XML, the listCursor is also moved, in 171 // case we didn't need to move XML (the new element to be inserted happened to 172 // be the last one in order), we need to move this cursor 173 moreElements = listCursor.toNextSibling(); 174 } 175 // Reposition the head of the sorted list 176 headCursor.toParent(); 177 headCursor.toFirstChild(); 178 } 179 } 180 181 /** 182 * Implements a <code>java.util.Comparator</code> for comparing <code>QName</code>values. 183 * The namespace URIs are compared first and if they are equal, the local parts are compared. 184 * <p/> 185 * The constructor accepts an argument indicating whether the comparison order is the same as 186 * the lexicographic order of the strings or the reverse. 187 */ 188 public static final class QNameComparator implements Comparator 189 { 190 public static final int ASCENDING = 1; 191 public static final int DESCENDING = 2; 192 193 private int order; 194 195 public QNameComparator(int order) 196 { 197 this.order = order; 198 if (order != ASCENDING && order != DESCENDING) 199 throw new IllegalArgumentException("Please specify one of ASCENDING or DESCENDING "+ 200 "comparison orders"); 201 } 202 203 public int compare(Object o, Object o1) 204 { 205 XmlCursor cursor1 = (XmlCursor) o; 206 XmlCursor cursor2 = (XmlCursor) o1; 207 QName qname1 = cursor1.getName(); 208 QName qname2 = cursor2.getName(); 209 int qnameComparisonRes = qname1.getNamespaceURI().compareTo(qname2.getNamespaceURI()); 210 if (qnameComparisonRes == 0) 211 return order == ASCENDING ? 212 qname1.getLocalPart().compareTo(qname2.getLocalPart()) : 213 -qname1.getLocalPart().compareTo(qname2.getLocalPart()); 214 else 215 return order == ASCENDING ? qnameComparisonRes : -qnameComparisonRes; 216 } 217 } 218 } 219