/* Copyright 2009 The Apache Software Foundation | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.xmlbeans.samples.cursor; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.Comparator; | |
import org.apache.xmlbeans.XmlCursor; | |
import org.apache.xmlbeans.XmlException; | |
import org.apache.xmlbeans.XmlObject; | |
import javax.xml.namespace.QName; | |
/** | |
*/ | |
public final class XmlSort | |
{ | |
/** | |
* Receives an XML element instance and sorts the children of this | |
* element in lexicographical (by default) order. | |
* | |
* @param args An array in which the first item is a | |
* path to the XML instance file and the second item (optional) is | |
* an XPath inside the document identifying the element to be sorted | |
*/ | |
public static void main(String[] args) | |
{ | |
if (args.length < 1 || args.length > 2) | |
{ | |
System.out.println(" java XmlSort <XML_File> [<XPath>]"); | |
return; | |
} | |
File f = new File(args[0]); | |
try | |
{ | |
XmlObject docInstance = XmlObject.Factory.parse(f); | |
XmlObject element = null; | |
if (args.length > 1) | |
{ | |
String xpath = args[1]; | |
XmlObject[] result = docInstance.selectPath(xpath); | |
if (result.length == 0) | |
{ | |
System.out.println("ERROR: XPath \"" + xpath + "\" did not return any results"); | |
} | |
else if (result.length > 1) | |
{ | |
System.out.println("ERROR: XPath \"" + xpath + "\" returned more than one " + | |
"node (" + result.length + ")"); | |
} | |
else | |
element = result[0]; | |
} | |
else | |
{ | |
// Navigate to the root element | |
XmlCursor c = docInstance.newCursor(); | |
c.toFirstChild(); | |
element = c.getObject(); | |
c.dispose(); | |
} | |
if (element != null) | |
sort(element, new QNameComparator(QNameComparator.ASCENDING)); | |
System.out.println(docInstance.xmlText()); | |
} | |
catch (IOException ioe) | |
{ | |
System.out.println("ERROR: Could not open file: \"" + args[0] + "\": " + | |
ioe.getMessage()); | |
} | |
catch (XmlException xe) | |
{ | |
System.out.println("ERROR: Could not parse file: \"" + args[0] + "\": " + | |
xe.getMessage()); | |
} | |
} | |
/** | |
* Sorts the children of <code>element</code> according to the order indicated by the | |
* comparator. | |
* @param element the element whose content is to be sorted. Only element children are sorted, | |
* attributes are not touched. When elements are reordered, all the text, comments and PIs | |
* follow the element that they come immediately after. | |
* @param comp a comparator that is to be used when comparing the <code>QName</code>s of two | |
* elements. See {@link org.apache.xmlbeans.samples.cursor.XmlSort.QNameComparator} for a simple | |
* implementation that compares two elements based on the value of their QName, but more | |
* complicated implementations are possible, for instance, ones that compare two elements based | |
* on the value of a specifc attribute etc. | |
* @throws IllegalArgumentException if the input <code>XmlObject</code> does not represent | |
* an element | |
*/ | |
public static void sort(XmlObject element, Comparator comp) | |
{ | |
XmlCursor headCursor = element.newCursor(); | |
if (!headCursor.isStart()) | |
throw new IllegalStateException("The element parameter must point to a STARTDOC"); | |
// We use insertion sort to minimize the number of swaps, because each swap means | |
// moving a part of the document | |
/* headCursor points to the beginning of the list of the already sorted items and | |
listCursor points to the beginning of the list of unsorted items | |
At the beginning, headCursor points to the first element and listCursor points to the | |
second element. The algorithm ends when listCursor cannot be moved to the "next" | |
element in the unsorted list, i.e. the unsorted list becomes empty */ | |
boolean moved = headCursor.toFirstChild(); | |
if (!moved) | |
{ | |
// Cursor was not moved, which means that the given element has no children and | |
// therefore there is nothing to sort | |
return; | |
} | |
XmlCursor listCursor = headCursor.newCursor(); | |
boolean moreElements = listCursor.toNextSibling(); | |
while (moreElements) | |
{ | |
moved = false; | |
// While we can move the head of the unsorted list, it means that there are still | |
// items (elements) that need to be sorted | |
while (headCursor.comparePosition(listCursor) < 0) | |
{ | |
if (comp.compare(headCursor, listCursor) > 0) | |
{ | |
// We have found the position in the sorted list, insert the element and the | |
// text following the element in the current position | |
/* | |
* Uncomment this code to cause the text before the element to move along | |
* with the element, rather than the text after the element. Notice that this | |
* is more difficult to do, because the cursor's "type" refers to the position | |
* to the right of the cursor, so to get the type of the token to the left, the | |
* cursor needs to be first moved to the left (previous token) | |
* | |
headCursor.toPrevToken(); | |
while (headCursor.isComment() || headCursor.isProcinst() || headCursor.isText()) | |
headCursor.toPrevToken(); | |
headCursor.toNextToken(); | |
listCursor.toPrevToken(); | |
while (listCursor.isComment() || listCursor.isProcinst() || listCursor.isText()) | |
listCursor.toPrevToken(); | |
listCursor.toNextToken(); | |
while (!listCursor.isStart()) | |
listCursor.moveXml(headCursor); | |
listCursor.moveXml(headCursor); | |
*/ | |
// Move the element | |
listCursor.moveXml(headCursor); | |
// Move the text following the element | |
while (!listCursor.isStart() && !listCursor.isEnd()) | |
listCursor.moveXml(headCursor); | |
moreElements = listCursor.isStart(); | |
moved = true; | |
break; | |
} | |
headCursor.toNextSibling(); | |
} | |
if (!moved) | |
{ | |
// Because during the move of a fragment of XML, the listCursor is also moved, in | |
// case we didn't need to move XML (the new element to be inserted happened to | |
// be the last one in order), we need to move this cursor | |
moreElements = listCursor.toNextSibling(); | |
} | |
// Reposition the head of the sorted list | |
headCursor.toParent(); | |
headCursor.toFirstChild(); | |
} | |
} | |
/** | |
* Implements a <code>java.util.Comparator</code> for comparing <code>QName</code>values. | |
* The namespace URIs are compared first and if they are equal, the local parts are compared. | |
* <p/> | |
* The constructor accepts an argument indicating whether the comparison order is the same as | |
* the lexicographic order of the strings or the reverse. | |
*/ | |
public static final class QNameComparator implements Comparator | |
{ | |
public static final int ASCENDING = 1; | |
public static final int DESCENDING = 2; | |
private int order; | |
public QNameComparator(int order) | |
{ | |
this.order = order; | |
if (order != ASCENDING && order != DESCENDING) | |
throw new IllegalArgumentException("Please specify one of ASCENDING or DESCENDING "+ | |
"comparison orders"); | |
} | |
public int compare(Object o, Object o1) | |
{ | |
XmlCursor cursor1 = (XmlCursor) o; | |
XmlCursor cursor2 = (XmlCursor) o1; | |
QName qname1 = cursor1.getName(); | |
QName qname2 = cursor2.getName(); | |
int qnameComparisonRes = qname1.getNamespaceURI().compareTo(qname2.getNamespaceURI()); | |
if (qnameComparisonRes == 0) | |
return order == ASCENDING ? | |
qname1.getLocalPart().compareTo(qname2.getLocalPart()) : | |
-qname1.getLocalPart().compareTo(qname2.getLocalPart()); | |
else | |
return order == ASCENDING ? qnameComparisonRes : -qnameComparisonRes; | |
} | |
} | |
} | |