Patch from Mark Olesen from bug #57552: Sort PackagePart returns from OPCPackage by name considering numbers in filenames, so Image10.png comes after Image9.png, fixing problems with XSLF adding 10+ images to a slide

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1677373 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2015-05-03 08:47:50 +00:00
parent 3543d1a553
commit c8f8cde62d
4 changed files with 153 additions and 24 deletions

View File

@ -29,6 +29,7 @@ import java.io.OutputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List; import java.util.List;
@ -581,6 +582,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
if (part.getContentType().equals(contentType)) if (part.getContentType().equals(contentType))
retArr.add(part); retArr.add(part);
} }
Collections.sort(retArr);
return retArr; return retArr;
} }
@ -604,22 +606,31 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
retArr.add(part); retArr.add(part);
} }
} }
Collections.sort(retArr);
return retArr; return retArr;
} }
/**
* Retrieve parts by name
*
* @param namePattern
* The pattern for matching the names
* @return All parts associated to the specified content type, sorted
* in alphanumerically by the part-name
*/
public List<PackagePart> getPartsByName(final Pattern namePattern) { public List<PackagePart> getPartsByName(final Pattern namePattern) {
if (namePattern == null) { if (namePattern == null) {
throw new IllegalArgumentException("name pattern must not be null"); throw new IllegalArgumentException("name pattern must not be null");
} }
Matcher matcher = namePattern.matcher("");
ArrayList<PackagePart> result = new ArrayList<PackagePart>(); ArrayList<PackagePart> result = new ArrayList<PackagePart>();
for (PackagePart part : partList.values()) { for (PackagePart part : partList.values()) {
PackagePartName partName = part.getPartName(); PackagePartName partName = part.getPartName();
String name = partName.getName(); if (matcher.reset(partName.getName()).matches()) {
Matcher matcher = namePattern.matcher(name);
if (matcher.matches()) {
result.add(part); result.add(part);
} }
} }
Collections.sort(result);
return result; return result;
} }
@ -727,7 +738,9 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
} }
} }
} }
return new ArrayList<PackagePart>(partList.values()); ArrayList<PackagePart> result = new ArrayList<PackagePart>(partList.values());
java.util.Collections.sort(result);
return result;
} }
/** /**

View File

@ -30,11 +30,8 @@ import org.apache.poi.openxml4j.opc.internal.ContentType;
/** /**
* Provides a base class for parts stored in a Package. * Provides a base class for parts stored in a Package.
*
* @author Julien Chable
* @version 0.9
*/ */
public abstract class PackagePart implements RelationshipSource { public abstract class PackagePart implements RelationshipSource, Comparable<PackagePart> {
/** /**
* This part's container. * This part's container.
@ -650,6 +647,19 @@ public abstract class PackagePart implements RelationshipSource {
+ this._contentType.toString(); + this._contentType.toString();
} }
/**
* Compare based on the package part name, using a natural sort order
*/
@Override
public int compareTo(PackagePart other)
{
// NOTE could also throw a NullPointerException() if desired
if (other == null)
return -1;
return PackagePartName.compare(this._partName, other._partName);
}
/*-------------- Abstract methods ------------- */ /*-------------- Abstract methods ------------- */
/** /**

View File

@ -17,6 +17,7 @@
package org.apache.poi.openxml4j.opc; package org.apache.poi.openxml4j.opc;
import java.math.BigInteger;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -428,19 +429,20 @@ public final class PackagePartName implements Comparable<PackagePartName> {
} }
/** /**
* Compare two part name following the rule M1.12 : * Compare two part names following the rule M1.12 :
* *
* Part name equivalence is determined by comparing part names as * Part name equivalence is determined by comparing part names as
* case-insensitive ASCII strings. Packages shall not contain equivalent * case-insensitive ASCII strings. Packages shall not contain equivalent
* part names and package implementers shall neither create nor recognize * part names and package implementers shall neither create nor recognize
* packages with equivalent part names. [M1.12] * packages with equivalent part names. [M1.12]
*/ */
public int compareTo(PackagePartName otherPartName) { @Override
if (otherPartName == null) public int compareTo(PackagePartName other)
return -1; {
return this.partNameURI.toASCIIString().toLowerCase().compareTo( // compare with natural sort order
otherPartName.partNameURI.toASCIIString().toLowerCase()); return compare(this, other);
} }
/** /**
* Retrieves the extension of the part name if any. If there is no extension * Retrieves the extension of the part name if any. If there is no extension
@ -474,14 +476,17 @@ public final class PackagePartName implements Comparable<PackagePartName> {
* packages with equivalent part names. [M1.12] * packages with equivalent part names. [M1.12]
*/ */
@Override @Override
public boolean equals(Object otherPartName) { public boolean equals(Object other) {
if (otherPartName == null if (other instanceof PackagePartName) {
|| !(otherPartName instanceof PackagePartName)) // String.equals() is compatible with our compareTo(), but cheaper
return false; return this.partNameURI.toASCIIString().toLowerCase().equals
return this.partNameURI.toASCIIString().toLowerCase().equals( (
((PackagePartName) otherPartName).partNameURI.toASCIIString() ((PackagePartName) other).partNameURI.toASCIIString().toLowerCase()
.toLowerCase()); );
} } else {
return false;
}
}
@Override @Override
public int hashCode() { public int hashCode() {
@ -503,4 +508,106 @@ public final class PackagePartName implements Comparable<PackagePartName> {
public URI getURI() { public URI getURI() {
return this.partNameURI; return this.partNameURI;
} }
/**
* A natural sort order for package part names, consistent with the
* requirements of {@code java.util.Comparator}, but simply implemented
* as a static method.
* <p>
* For example, this sorts "file10.png" after "file2.png" (comparing the
* numerical portion), but sorts "File10.png" before "file2.png"
* (lexigraphical sort)
*
* <p>
* When comparing part names, the rule M1.12 is followed:
*
* Part name equivalence is determined by comparing part names as
* case-insensitive ASCII strings. Packages shall not contain equivalent
* part names and package implementers shall neither create nor recognize
* packages with equivalent part names. [M1.12]
*/
public static int compare(PackagePartName obj1, PackagePartName obj2)
{
// NOTE could also throw a NullPointerException() if desired
if (obj1 == null)
{
// (null) == (null), (null) < (non-null)
return (obj2 == null ? 0 : -1);
}
else if (obj2 == null)
{
// (non-null) > (null)
return 1;
}
return compare
(
obj1.getURI().toASCIIString().toLowerCase(),
obj2.getURI().toASCIIString().toLowerCase()
);
}
/**
* A natural sort order for strings, consistent with the
* requirements of {@code java.util.Comparator}, but simply implemented
* as a static method.
* <p>
* For example, this sorts "file10.png" after "file2.png" (comparing the
* numerical portion), but sorts "File10.png" before "file2.png"
* (lexigraphical sort)
*/
public static int compare(String str1, String str2)
{
if (str1 == null)
{
// (null) == (null), (null) < (non-null)
return (str2 == null ? 0 : -1);
}
else if (str2 == null)
{
// (non-null) > (null)
return 1;
}
int len1 = str1.length();
int len2 = str2.length();
for (int idx1 = 0, idx2 = 0; idx1 < len1 && idx2 < len2; /*nil*/)
{
char c1 = str1.charAt(idx1++);
char c2 = str2.charAt(idx2++);
if (Character.isDigit(c1) && Character.isDigit(c2))
{
int beg1 = idx1 - 1; // undo previous increment
while (idx1 < len1 && Character.isDigit(str1.charAt(idx1)))
{
++idx1;
}
int beg2 = idx2 - 1; // undo previous increment
while (idx2 < len2 && Character.isDigit(str2.charAt(idx2)))
{
++idx2;
}
// note: BigInteger for extra safety
int cmp = new BigInteger(str1.substring(beg1, idx1)).compareTo
(
new BigInteger(str2.substring(beg2, idx2))
);
if (cmp != 0) return cmp;
}
else if (c1 != c2)
{
return (c1 - c2);
}
}
return (len1 - len2);
}
} }
/* ************************************************************************** */

View File

@ -265,7 +265,6 @@ public class TestXSLFBugs {
* that image10.foo isn't between image1.foo and image2.foo * that image10.foo isn't between image1.foo and image2.foo
*/ */
@Test @Test
@Ignore
public void test57552() throws Exception { public void test57552() throws Exception {
XMLSlideShow ss = new XMLSlideShow(); XMLSlideShow ss = new XMLSlideShow();
for (String s : new String[]{"Slide1","Slide2"}) { for (String s : new String[]{"Slide1","Slide2"}) {