/* * Copyright (c) 2006, Wygwam * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * - Neither the name of Wygwam nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.docx4j.openpackaging; import java.net.URI; import java.net.URISyntaxException; import org.docx4j.openpackaging.exceptions.InvalidFormatException; import org.docx4j.openpackaging.parts.PartName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helper for part and pack URI. * * @author Julien Chable, CDubet * @version 0.3 */ public final class URIHelper { private static Logger log = LoggerFactory.getLogger(URIHelper.class); /** * Package root URI. */ private static URI packageRootUri; /** * Extension name of a relationship part. */ public static final String RELATIONSHIP_PART_EXTENSION_NAME; /** * Segment name of a relationship part. */ public static final String RELATIONSHIP_PART_SEGMENT_NAME; /** * Segment name of the package properties folder. */ public static final String PACKAGE_PROPERTIES_SEGMENT_NAME; /** * Core package properties art name. */ public static final String PACKAGE_CORE_PROPERTIES_NAME; /** * Forward slash URI separator. */ public static final char FORWARD_SLASH_CHAR; /** * Forward slash URI separator. */ public static final String FORWARD_SLASH_STRING; /** * Package relationships part URI */ public static final URI PACKAGE_RELATIONSHIPS_ROOT_URI; /** * Package relationships part name. */ public static final PartName PACKAGE_RELATIONSHIPS_ROOT_PART_NAME; /** * Core properties part URI. */ public static final URI CORE_PROPERTIES_URI; /** * Core properties partname. */ public static final PartName CORE_PROPERTIES_PART_NAME; /** * Root package URI. */ public static final URI PACKAGE_ROOT_URI; /** * Root package part name. */ public static final PartName PACKAGE_ROOT_PART_NAME; /* Static initialization */ static { RELATIONSHIP_PART_SEGMENT_NAME = "_rels"; RELATIONSHIP_PART_EXTENSION_NAME = ".rels"; FORWARD_SLASH_CHAR = '/'; FORWARD_SLASH_STRING = "/"; PACKAGE_PROPERTIES_SEGMENT_NAME = "docProps"; PACKAGE_CORE_PROPERTIES_NAME = "core.xml"; // Make URI URI uriPACKAGE_ROOT_URI = null; URI uriPACKAGE_RELATIONSHIPS_ROOT_URI = null; URI uriPACKAGE_PROPERTIES_URI = null; try { uriPACKAGE_ROOT_URI = new URI("/"); uriPACKAGE_RELATIONSHIPS_ROOT_URI = new URI(FORWARD_SLASH_CHAR + RELATIONSHIP_PART_SEGMENT_NAME + FORWARD_SLASH_CHAR + RELATIONSHIP_PART_EXTENSION_NAME); packageRootUri = new URI("/"); uriPACKAGE_PROPERTIES_URI = new URI(FORWARD_SLASH_CHAR + PACKAGE_PROPERTIES_SEGMENT_NAME + FORWARD_SLASH_CHAR + PACKAGE_CORE_PROPERTIES_NAME); } catch (URISyntaxException e) { // Should never happen in production as all data are fixed } PACKAGE_ROOT_URI = uriPACKAGE_ROOT_URI; PACKAGE_RELATIONSHIPS_ROOT_URI = uriPACKAGE_RELATIONSHIPS_ROOT_URI; CORE_PROPERTIES_URI = uriPACKAGE_PROPERTIES_URI; // Make part name from previous URI PartName tmpPACKAGE_ROOT_PART_NAME = null; PartName tmpPACKAGE_RELATIONSHIPS_ROOT_PART_NAME = null; PartName tmpCORE_PROPERTIES_URI = null; try { tmpPACKAGE_RELATIONSHIPS_ROOT_PART_NAME = createPartName(PACKAGE_RELATIONSHIPS_ROOT_URI); tmpCORE_PROPERTIES_URI = createPartName(CORE_PROPERTIES_URI); tmpPACKAGE_ROOT_PART_NAME = new PartName(PACKAGE_ROOT_URI, false); } catch (InvalidFormatException e) { // Should never happen in production as all data are fixed } PACKAGE_RELATIONSHIPS_ROOT_PART_NAME = tmpPACKAGE_RELATIONSHIPS_ROOT_PART_NAME; CORE_PROPERTIES_PART_NAME = tmpCORE_PROPERTIES_URI; PACKAGE_ROOT_PART_NAME = tmpPACKAGE_ROOT_PART_NAME; } /** * Gets the URI for the package root. * * @return URI of the package root. */ public static URI getPackageRootUri() { return packageRootUri; } /** * Know if the specified URI is a relationship part name. * * @param partUri * URI to check. * @return <i>true</i> if the URI <i>false</i>. */ private static boolean isRelationshipPartURI(URI partUri) { if (partUri == null) throw new NullPointerException("partUri"); return partUri.getPath().matches( ".*" + RELATIONSHIP_PART_SEGMENT_NAME + ".*" + RELATIONSHIP_PART_EXTENSION_NAME + "$"); } /** * Get file name from the specified URI. */ public static String getFilename(URI uri) { if (uri != null) { String path = uri.getPath(); int len = path.length(); int num2 = len; while (--num2 >= 0) { char ch1 = path.charAt(num2); if (ch1 == URIHelper.FORWARD_SLASH_CHAR) return path.substring(num2 + 1, len); } } return ""; } /** * Get the file name without the trailing extension. */ public static String getFilenameWithoutExtension(URI uri) { String filename = getFilename(uri); int dotIndex = filename.lastIndexOf("."); if (dotIndex == -1) return filename; return filename.substring(0, dotIndex); } /** * Get the directory path from the specified URI. */ public static URI getPath(URI uri) { if (uri != null) { String path = uri.getPath(); int len = path.length(); int num2 = len; while (--num2 >= 0) { char ch1 = path.charAt(num2); if (ch1 == URIHelper.FORWARD_SLASH_CHAR) { try { return new URI(path.substring(0, num2)); } catch (URISyntaxException e) { return null; } } } } return null; } /** * Combine two URI. * * @param prefix * @param suffix * @return */ public static URI combine(URI prefix, URI suffix) { URI retUri = null; try { retUri = new URI(combine(prefix.getPath(), suffix.getPath())); } catch (URISyntaxException e) { throw new IllegalArgumentException( "Prefix and suffix can't be combined !"); } return retUri; } /** * Combine a string URI with a prefix and a suffix. */ public static String combine(String prefix, String suffix) { if (!prefix.endsWith("" + FORWARD_SLASH_CHAR) && !suffix.startsWith("" + FORWARD_SLASH_CHAR)) return prefix + FORWARD_SLASH_CHAR + suffix; else if ((!prefix.endsWith("" + FORWARD_SLASH_CHAR) && suffix.startsWith("" + FORWARD_SLASH_CHAR) || (prefix .endsWith("" + FORWARD_SLASH_CHAR) && !suffix.startsWith("" + FORWARD_SLASH_CHAR)))) return prefix + suffix; else return ""; } /** * Fully relativize the target part URI against the source part URI. * * @param sourceURI * The source part URI. * @param targetURI * The target part URI. * @return A fully relativize part name URI ('word/media/image1.gif', * '/word/document.xml' => 'media/image1.gif') else * <code>null</code>. */ public static URI relativizeURI(URI sourceURI, URI targetURI) { StringBuilder retVal = new StringBuilder(); String[] segmentsSource = sourceURI.getPath().split("/", -1); String[] segmentsTarget = targetURI.getPath().split("/", -1); // If the source URI is empty if (segmentsSource.length == 0) { throw new IllegalArgumentException( "Can't relativize an empty source URI !"); } // If target URI is empty if (segmentsTarget.length == 0) { throw new IllegalArgumentException( "Can't relativize an empty target URI !"); } // If the source is the root, then the relativized // form must actually be an absolute URI if(sourceURI.toString().equals("/")) { return targetURI; } // Relativize the source URI against the target URI. // First up, figure out how many steps along we can go // and still have them be the same int segmentsTheSame = 0; for (int i = 0; i < segmentsSource.length && i < segmentsTarget.length; i++) { if (segmentsSource[i].equals(segmentsTarget[i])) { // Match so far, good segmentsTheSame++; } else { break; } } // If we didn't have a good match or at least except a first empty element if ((segmentsTheSame == 0 || segmentsTheSame == 1) && segmentsSource[0].equals("") && segmentsTarget[0].equals("")) { for (int i = 0; i < segmentsSource.length - 2; i++) { retVal.append("../"); } for (int i = 0; i < segmentsTarget.length; i++) { if (segmentsTarget[i].equals("")) continue; retVal.append(segmentsTarget[i]); if (i != segmentsTarget.length - 1) retVal.append("/"); } try { return new URI(retVal.toString()); } catch (Exception e) { System.err.println(e); return null; } } // Special case for where the two are the same if (segmentsTheSame == segmentsSource.length && segmentsTheSame == segmentsTarget.length) { retVal.append(""); } else { // Matched for so long, but no more // Do we need to go up a directory or two from // the source to get here? // (If it's all the way up, then don't bother!) if (segmentsTheSame == 1) { retVal.append("/"); } else { for (int j = segmentsTheSame; j < segmentsSource.length - 1; j++) { retVal.append("../"); } } // Now go from here on down for (int j = segmentsTheSame; j < segmentsTarget.length; j++) { if (retVal.length() > 0 && retVal.charAt(retVal.length() - 1) != '/') { retVal.append("/"); } retVal.append(segmentsTarget[j]); } } try { return new URI(retVal.toString()); } catch (Exception e) { System.err.println(e); return null; } } /** * Resolve a target uri against a source. * * @param sourcePartUri * The source URI. * @param targetUri * The target URI. * @return The resolved URI. */ public static URI resolvePartUri(URI sourcePartUri, URI targetUri) { // log.info("source: " + sourcePartUri); // log.info("target: " + targetUri); URI uri; if (sourcePartUri == null || sourcePartUri.isAbsolute()) { throw new IllegalArgumentException("sourcePartUri"); } if (targetUri == null) { log.error("targetUri was null"); throw new IllegalArgumentException("targetUri"); } else if (targetUri.isAbsolute()) { log.error("targetUri " + targetUri.toString() + " is absolute!"); throw new IllegalArgumentException("targetUri"); } uri = sourcePartUri.resolve(targetUri); // log.info("RESULT: " + uri); return uri; } /** * Get URI from a string path. */ public static URI getURIFromPath(String path) { URI retUri = null; try { retUri = new URI(path); } catch (URISyntaxException e) { throw new IllegalArgumentException("path"); } return retUri; } public static URI getSourcePartUriFromRelationshipPartUri( URI relationshipPartUri) { if (relationshipPartUri == null) throw new IllegalArgumentException( "The relationshipPart Uri was null !"); if (!isRelationshipPartURI(relationshipPartUri)) throw new IllegalArgumentException( "L'URI ne doit pas �tre celle d'une partie de type relation."); if (relationshipPartUri.compareTo(PACKAGE_RELATIONSHIPS_ROOT_URI) == 0) return PACKAGE_ROOT_URI; String filename = relationshipPartUri.getPath(); String filenameWithoutExtension = getFilenameWithoutExtension(relationshipPartUri); filename = filename .substring(0, ((filename.length() - filenameWithoutExtension .length()) - RELATIONSHIP_PART_EXTENSION_NAME.length())); filename = filename.substring(0, filename.length() - RELATIONSHIP_PART_SEGMENT_NAME.length() - 1); filename = combine(filename, filenameWithoutExtension); return getURIFromPath(filename); } /** * Create an OPC compliant part name by throwing an exception if the URI is * not valid. * * @param partUri * The part name URI to validate. * @return A valid part name object, else <code>null</code>. * @throws InvalidFormatException * Throws if the specified URI is not OPC compliant. */ public static PartName createPartName(URI partUri) throws InvalidFormatException { if (partUri == null) throw new IllegalArgumentException("partName"); return new PartName(partUri, true); } /** * Create an OPC compliant part name by throwing an exception if the * specified name is not valid. * * @param partName * The part name to validate. * @return The correspondant part name if valid, else <code>null</code>. * @throws InvalidFormatException * Throws if the specified part name is not OPC compliant. * @see #createPartName(URI) */ public static PartName createPartName(String partName) throws InvalidFormatException { URI partNameURI; try { partNameURI = new URI(partName); } catch (URISyntaxException e) { throw new InvalidFormatException(e.getMessage()); } return createPartName(partNameURI); } /** * Validate a part URI by returning a boolean. * ([M1.1],[M1.3],[M1.4],[M1.5],[M1.6]) * * (OPC Specifications 8.1.1 Part names) : * * Part Name Syntax * * The part name grammar is defined as follows: * * <i>part_name = 1*( "/" segment ) * * segment = 1*( pchar )</i> * * * (pchar is defined in RFC 3986) * * @param partUri * The URI to validate. * @return <b>true</b> if the URI is valid to the OPC Specifications, else * <b>false</b> * * @see #createPartName(URI) */ public static boolean isValidPartName(URI partUri) { if (partUri == null) throw new IllegalArgumentException("partUri"); try { createPartName(partUri); return true; } catch (Exception e) { return false; } } /** * Decode a URI by converting all percent encoded character into a String * character. * * @param uri * The URI to decode. * @return The specified URI in a String with converted percent encoded * characters. */ public static String decodeURI(URI uri) { StringBuffer retVal = new StringBuffer(); String uriStr = uri.toASCIIString(); char c; for (int i = 0; i < uriStr.length(); ++i) { c = uriStr.charAt(i); if (c == '%') { // We certainly found an encoded character, check for length // now ( '%' HEXDIGIT HEXDIGIT) if (((uriStr.length() - i) < 2)) { throw new IllegalArgumentException("The uri " + uriStr + " contain invalid encoded character !"); } // Decode the encoded character char decodedChar = (char) Integer.parseInt(uriStr.substring( i + 1, i + 3), 16); retVal.append(decodedChar); i += 2; continue; } retVal.append(c); } return retVal.toString(); } // /** // * Fully relativize the source part URI against the target part URI. // * // * @param sourceURI // * The source part URI. // * @param targetURI // * The target part URI. // * @return A fully relativize part name URI ('word/media/image1.gif', // * '/word/document.xml' => 'media/image1.gif') else // * <code>null</code>. // */ // public static URI OLDrelativizeURI(URI sourceURI, URI targetURI) { // StringBuffer retVal = new StringBuffer(); // String[] segmentsSource = sourceURI.getPath().split("/"); // String[] segmentsTarget = targetURI.getPath().split("/"); // // // If the source URI is empty // if (segmentsSource.length == 0) { // return null; // } // // // If target URI is empty // if (segmentsTarget.length == 0) { // if (sourceURI.getPath().startsWith(FORWARD_SLASH_STRING)) { // try { // return new URI(sourceURI.getPath().substring(1)); // } catch (Exception e) { // return null; // } // } else // return sourceURI; // } // // // Relativize the source URI against the target URI. // for (short i = 0, j = 0; i < segmentsSource.length // && j < segmentsTarget.length; ++i, ++j) { // if (segmentsSource[i].equalsIgnoreCase(segmentsTarget[j])) { // if (i < segmentsSource.length - 1) { // continue; // } else { // // We add the last segment whatever it happens // retVal.append("/"); // retVal.append(segmentsTarget[i]); // break; // } // } else { // for (; i < segmentsSource.length; ++i) { // retVal.append("/"); // retVal.append(segmentsSource[i]); // } // break; // } // } // try { // PartName retPartName = new PartName( // retVal.toString(), true); // return new URI(retPartName.getURI().getPath().substring(1)); // } catch (Exception e) { // return null; // } // } }