/* * [Tools.java] * * Summary: common tools for specific HTML macros, also configuring constants. * * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.8 2009-02-06 include go package in ZIP bundle. * 1.9 2014-09-28 replace methods with EIO.getExtension and EIO.getCoreName */ package com.mindprod.htmlmacros.support; import com.mindprod.common18.EIO; import com.mindprod.common18.ST; import com.mindprod.entities.DeEntifyStrings; import com.mindprod.fastcat.FastCat; import com.mindprod.htmlmacros.macro.Global; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.regex.Pattern; import static com.mindprod.htmlmacros.macro.Global.configuration; import static java.lang.System.*; /** * common tools for specific HTML macros, also configuring constants. * * @author Roedy Green, Canadian Mind Products * @version 1.9 2014-09-28 replace methods with EIO.getExtension and EIO.getCoreName * @see BuildImage * @since 2009 */ @SuppressWarnings( { "UnusedDeclaration" } ) public final class Tools { // declarations /** * true if we are debugging. Gets extra output */ private static final boolean DEBUGGING = false; /** * Pattern to split path into directory segments separated by / */ private static final Pattern SPLIT_ON_SLASH = Pattern.compile( "/" ); /** * webroot name with lead E:/ as file */ static File rootDir = new File( configuration.getLocalWebrootWithSlashes().toLowerCase() ); /** * webroot name with lead E:\ */ private static String rootDirNameWithBackSlashes = configuration.getLocalWebrootWithBackslashes().toLowerCase(); /** * webroot name with lead E:/ */ private static String rootDirNameWithSlashes = configuration.getLocalWebrootWithSlashes().toLowerCase(); // /declarations // methods /** * Used by completeLink * expandNoRef expandNoRef to a full link * Used to help implement cookedLinkWithSlashes. Handles absolute URL case. * * @param absoluteURL URL starting with http:// * @param description text description of link. * @param linkCSSClass css class of the link. null will be treated as offsite * @param target which frame to use e.g "_blank" * * @return href to that link * @see #relativeURL(String, java.io.File) */ @SuppressWarnings( { "OverloadedMethodsWithSameNumberOfParameters" } ) private static String completeOffsiteLink( String absoluteURL, String description, @Nullable String linkCSSClass, final String target ) { if ( linkCSSClass == null || linkCSSClass.length() == 0 ) { linkCSSClass = Global.assignCSSClasses.assignCSSClass( absoluteURL, null ); } final FastCat sb = new FastCat( 12 ); sb.append( " 0 && !configuration.isUsingXHTML() ) { sb.append( "target=\"" ); sb.append( target ); sb.append( "\" " ); } if ( linkCSSClass.length() > 0 ) { sb.append( "class=\"" ); sb.append( linkCSSClass ); sb.append( "\" " ); } sb.append( "href=\"" ); sb.append( absoluteURL ); sb.append( "\">" ); sb.append( description ); sb.append( "" ); return sb.toString(); } // /methods /** * Get the pieces of the directory name, webroot relative. * * @param dir directory name, not fileBeingDistributed Get array of directory segments that make up the directory, * possibly empty. livinglove/methods gives { "livinglove" "methods" } * * @return just segments of directory name. */ @SuppressWarnings( { "WeakerAccess" } ) private static String[] dirSegments( String dir ) { if ( dir == null || dir.length() == 0 ) { // split would return one empty element // in this case instead of empty array return new String[ 0 ]; } else { if ( dir.indexOf( '/' ) < 0 ) { // faster than splitter, the normal case return new String[] { dir }; } else { // two or more directory segments return SPLIT_ON_SLASH.split( dir ); } } } // /methods /** * Get string representing how deeply nested we are from the root. * * @param level depth we are from webRoot. * * @return empty for root, ../ for one deep, ../../ for two deep * @see #relativeURL */ private static String getLevelString( int level ) { if ( level < 0 ) { throw new IllegalArgumentException( "Tools.getLevelString: invalid directory nesting level " + level ); } switch ( level ) { case 0: return ""; case 1: return "../"; case 2: return "../../"; default: final FastCat sb = new FastCat( level ); for ( int i = 0; i < level; i++ ) { sb.append( "../" ); } return sb.toString(); } // end switch } // /methods /** * Test link method to generate relative references * * @param fromFilename filename relative to webRoot * @param toFilename toFilename relative to webRoot */ private static void linkTest( String fromFilename, String toFilename ) { if ( DEBUGGING ) { final File fromFile = new File( rootDir, fromFilename ); out.println( "link test: from: [" + fromFilename + "] to: [" + toFilename + "] relative link: [" + relativeURL( toFilename, fromFile ) + "]" ); } } // /methods /** * Excercise the methods on a given filename relative to the webRoot. * * @param filename webRoot relative filename to TEST methods on */ private static void test( String filename ) { if ( DEBUGGING ) { final File f = new File( rootDir, filename ); out.println( "--------------------------------------------" ); out.println( "Testing file: [" + f + "]" ); out.println( "basicNameWithExtension: [" + basicNameWithExtension( f ) + "]" ); out.println( "coreName: [" + EIO.getCoreName( f ) + "]" ); out.println( "qualifiedCanonicalNameWithBackslashes: [" + qualifiedCanonicalNameWithBackslashes( f ) + "]" ); out.println( "dirWithSlashes: [" + dirWithSlashes( f ) + "]" ); String[] dirs = dirSegments( dirWithSlashes( f ) ); out.println( "segments: [" + dirs.length + "]" ); for ( String dir : dirs ) { out.println( "seg: [" + dir + "]" ); } out.println( "extension: [" + EIO.getExtension( f ) + "]" ); out.println( "qualifiedNameWithBackslashes: [" + qualifiedNameWithBackslashes( f ) + "]" ); out.println( "getParent: [" + getParent( f ) + "]" ); out.println( "serverQualifiedURLWithSlashes: [" + serverQualifiedURLWithSlashes( f ) + "]" ); out.println( "uPathName: [" + uPathName( f ) + "]" ); } } // /methods /** * Get name.ext without path. Not Canonical. Will not contain any / or \. * e.g. regex.html * * @param fileBeingDistributed the file having its macros expanded * * @return unqualified filename e.g. bushisms.html. */ @SuppressWarnings( { "WeakerAccess" } ) public static String basicNameWithExtension( File fileBeingDistributed ) { return fileBeingDistributed.getName(); } // /methods /** * Used to fine tune initial StringBuilder size estimates. * Insert a call to checkStringBuilderEstimate just before toString. * * @param sb the StringBuilder to check. * @param lowSize the lowest expected size of a result with this StringBuilder. * @param highSize usually the initial size the StringBuilder was allocated, * @param fileBeingDistributed the file currently being processed. */ @SuppressWarnings( { "ThrowableInstanceNeverThrown" } ) public static void checkStringBuilderEstimate( StringBuilder sb, int lowSize, int highSize, @Nullable final File fileBeingDistributed ) { final int size = sb.length(); final String howDifferent; if ( size > highSize ) { howDifferent = "longer"; } else if ( size < lowSize ) { howDifferent = "shorter"; } else { return; } err.println(); // to get on fresh line from letter progress index. if ( configuration.isUsingJet() ) { // Jet does not let us look at a stack trace. We thus don't know where the call came from. // In theory we turn turn better tracing on, but that defeats the point of lean and mean. err.println( "Warning: StringBuilder length " + size + " is " + howDifferent + " than the expected range " + lowSize + ".." + highSize + ( fileBeingDistributed == null ? "" : ( " in file " + fileBeingDistributed.toString() ) ) + ". Run under Oracle JVM to find out where the problem lies." ); } else { // Oracle JVM lets us figure out where the call came from. StackTraceElement e = new Throwable().getStackTrace()[ 1 ]; err.println( "Warning: " + e.getClassName() + "." + e.getMethodName() + "\n " + " line:" + e.getLineNumber() + " StringBuilder length " + size + " is " + howDifferent + " than the expected range " + lowSize + ".." + highSize + ( fileBeingDistributed == null ? "" : ( " in file " + fileBeingDistributed.toString() ) ) + "." ); } } // /methods /** * expandNoRef expandNoRef to <a class="linkclass">description</a> * * @param uPathURL webroot relative link, (also works with absolute URL http:// * @param description text description of link. Could be 0 && !configuration.isUsingXHTML() ) { sb.append( "target=\"" ); sb.append( frame ); sb.append( "\" " ); } if ( linkCSSClass != null && linkCSSClass.length() > 0 ) { sb.append( "class=\"" ); sb.append( linkCSSClass ); sb.append( "\" " ); } sb.append( "href=\"" ); sb.append( Tools.relativeURL( uPathURL, fileBeingDistributed ) ); sb.append( "\">" ); sb.append( description ); sb.append( "" ); return sb.toString(); } } // /methods /** * expandNoRef expandNoRef to <a class="linkclass">description</a> * * @param uPathURL webroot relative link, (also works with absolute URL http:// * @param description text description of link. (might be E:\mindprod\jgloss\jgloss.html * E:\mindprod\jgloss\master --> E:\mindprod\jgloss * Does not check if either file exists. * * @param file master file * * @return master file, as a File not a String. */ public static File correspondingDistributable( File file ) { final String fn = EIO.getCanOrAbsPath( file ); assert !fn.endsWith( "\\" ) : "correspondingMaster file ends with \\"; assert !fn.contains( "/" ) : "correspondingMaster file contains /"; if ( fn.endsWith( ".html" ) ) { final int place = fn.lastIndexOf( "\\master\\" ); if ( place >= 0 ) { // chop out master but leave \ return new File( fn.substring( 0, place ) + fn.substring( place + "\\master".length() ) ); } else { throw new IllegalArgumentException( "correspondingDistributable: illegal filename " + fn ); } } else { // it is a directory return new File( ST.chopTrailingString( fn, "\\master" ) ); } } // /methods /** * Get the corresponding master file for this distributed html file e.g. * E:\mindprod\jgloss\jgloss.html --> E:\mindprod\jgloss\master\jgloss.html * E:\mindprod\jgloss --> E:\mindprod\jgloss\master * Does not check if either file exists. * * @param file distributed file * * @return master file, as a File not a String. */ public static File correspondingMaster( File file ) { final String fn = EIO.getCanOrAbsPath( file ); assert !fn.endsWith( "\\" ) : "correspondingMaster file ends with \\"; assert !fn.contains( "/" ) : "correspondingMaster file contains /"; if ( fn.endsWith( ".html" ) ) { final int place = fn.lastIndexOf( '\\' ); if ( place >= 0 ) { return new File( fn.substring( 0, place ) + "\\master" + fn.substring( place ) ); } else { throw new IllegalArgumentException( "correspondingMaster: illegal filename " + fn ); } } else { // it is a directory return new File( fn + "\\master" ); } } // /methods /** * Get just the / dir name, relative to webroot. * * @param fileBeingDistributed the file having its macros expanded * * @return just directory part of the name without trailing /, with embedded /. e.g. politics, or politics/laser */ public static String dirWithSlashes( File fileBeingDistributed ) { assert fileBeingDistributed != null : "null fileBeingDistributed"; String path = uPathName( fileBeingDistributed ); int place = path.lastIndexOf( '/' ); if ( place < 0 ) { return ""; } else { return path.substring( 0, place ); } } // /methods /** * Strip quotes, tags, and   and entities, to allow content to be used in header fields * * @param content to flatten * * @return flattened text */ public static String flatten( String content ) { return stripQuotes( DeEntifyStrings.flattenHTML( content, ' ' ) ).trim(); } // /methods /** * Currently duplicated in com.mindprod.commandline.CommandLine Smarter version of File.getParent. It has yet to be * tested with all combinations of absolute, relative, root, ., .., ../ ../sub C: E:\ etc. * * @param fileBeingDistributed the file having its macros expanded * * @return parent directory that file lives in as File object, ready to use in FilenameFilter.accept. */ @SuppressWarnings( { "WeakerAccess" } ) public static File getParent( File fileBeingDistributed ) { String parent = fileBeingDistributed.getParent(); if ( parent == null || parent.length() == 0 ) { // Oracle's method failed, try another. String fullName = EIO.getCanOrAbsPath( fileBeingDistributed ); String name = fileBeingDistributed.getName(); if ( name == null ) { name = ""; } parent = fullName.substring( 0, fullName.length() - name.length() ); if ( parent == null || parent.length() == 0 ) { throw new IllegalArgumentException( "Tools:getParent failed" ); } } return new File( parent ); } // /methods /** * is this file of the form E:\mindprod\xxx\master\xxx.html * * @param file file/dir to test * * @return true if file in a master directory, e.g. a master containing comments and macros */ public static boolean isFileAMaster( File file ) { final String fn = EIO.getCanOrAbsPath( file ); return fn.contains( "\\master\\" ) || fn.endsWith( "\\master" ); } // /methods /** * TEST harness * * @param args not used */ @SuppressWarnings( { "UnusedParameters" } ) public static void main( String[] args ) { if ( DEBUGGING ) { out.println( isFileAMaster( new File( "E:\\mindprod\\jgloss\\jgloss.html" ) ) ); // debug test out.println( isFileAMaster( new File( "E:/mindprod/jgloss/jgloss.html" ) ) ); // debug test out.println( isFileAMaster( new File( "E:\\mindprod\\jgloss\\master\\jgloss.html" ) ) ); out.println( isFileAMaster( new File( "E:/mindprod/jgloss/master/jgloss.html" ) ) ); out.println( correspondingMaster( new File( "E:\\mindprod\\jgloss\\jgloss.html" ) ) ); out.println( correspondingDistributable( new File( "E:\\mindprod\\jgloss\\master\\jgloss.html" ) ) ); out.println( flatten( "Roedy’s" ) ); test( "livinglove/methods/icd.html" ); test( "livinglove/ll.html" ); test( "products.html" ); test( "kjv/2_Thessalonians/1.html" ); linkTest( "livinglove/methods/icd.html", "livinglove/methods/pws.html" ); linkTest( "livinglove/methods/icd.html", "livinglove/ll.html" ); linkTest( "livinglove/methods/icd.html", "products.html" ); linkTest( "livinglove/methods/pws.html", "livinglove/methods/icd.html" ); linkTest( "livinglove/ll.html", "livinglove/methods/icd.html" ); linkTest( "products.html", "livinglove/methods/icd.html" ); linkTest( "index.html", "products.html" ); linkTest( "index.html", "livinglove/ll.html" ); linkTest( "livinglove/ll.html", "livinglove/llbio.html" ); linkTest( "livinglove/ll.html", "politics/laser/freetrade.html" ); linkTest( "livinglove/ll.html", "animalrights/dolphin.html" ); } } /** * Get / name relative to webroot of a file in the same directory as the file being processed. * * @param nearbyFilename name of file in same directory as fileBeingDistributed * @param fileBeingDistributed the file having its macros expanded * * @return name relative to LOCAL_WEBROOT_WITH_BACKSLASHES with / in names e.g.politics/bushisms.html */ public static String nearbyUPathName( String nearbyFilename, File fileBeingDistributed ) { return uPathName( new File( fileBeingDistributed.getParent(), nearbyFilename ) ); } // /methods /** * convert spaces to   * * @param s String to convert, might contain tags * * @return String with all spaces made non-breaking. Easier with CSS white-space:nowrap */ public static String nonBreaking( String s ) { // leave as StringBuilder final StringBuilder sb = new StringBuilder( s.length() * 2 ); boolean insideTag = false; for ( int i = 0, n = s.length(); i < n; i++ ) { char c = s.charAt( i ); switch ( c ) { case ' ': if ( insideTag ) { sb.append( ' ' ); } else { sb.append( " " ); } break; case '<': insideTag = true; sb.append( '<' ); break; case '>': insideTag = false; sb.append( '>' ); break; default: sb.append( c ); } } checkStringBuilderEstimate( sb, s.length(), s.length() * 3, null ); // if did not increase length of string, we did not do anything, so return original. return sb.length() == s.length() ? s : sb.toString(); }// /methods /** * Get fully qualified name locally, path, name and ext. * e.g E:\mindprod\politics\bushisms.html * * @param fileBeingDistributed the file having its macros expanded * * @return fully qualified filename with \ in names e.g. E:\mindprod\politics\bushisms.html with caps in filename * exact. */ @SuppressWarnings( { "WeakerAccess" } ) public static String qualifiedCanonicalNameWithBackslashes( File fileBeingDistributed ) { return EIO.getCanOrAbsPath( fileBeingDistributed ); } // /methods /** * Get fully qualified name locally, path, name and ext., not canonical * E:\mindprod\politics\bushisms.html * * @param fileBeingDistributed the file having its macros expanded * * @return fully qualified filename with \ in names e.g. E:\mindprod\politics\bushisms.html with caps in filename * exact. */ @SuppressWarnings( { "WeakerAccess" } ) public static String qualifiedNameWithBackslashes( File fileBeingDistributed ) { assert fileBeingDistributed != null : "null fileBeingDistributed"; return EIO.getCanOrAbsPath( fileBeingDistributed ) .replace( '/', File.separatorChar ); } // /methods /** * Generate a relative url from the current file, no <a href= xxx>/a<etc. * * @param uPathFilename file where link jumps to, webRoot relative * @param fileBeingDistributed where the link will be embedded * * @return relative url, with slashes. * @see #completeLink */ public static String relativeURL( String uPathFilename, File fileBeingDistributed ) { assert fileBeingDistributed != null : "null fileBeingDistributed"; String[] sourceSegs = dirSegments( dirWithSlashes( fileBeingDistributed ) ); final File targetFile = new File( rootDir, uPathFilename ); String[] targetSegs = dirSegments( dirWithSlashes( targetFile ) ); // get base filename.ext#xxxxx String targetName = basicNameWithExtension( targetFile ); // eliminate high order commonality int last = Math.min( sourceSegs.length, targetSegs.length ); for ( int c = 0; c < last; c++ ) { if ( !sourceSegs[ c ].equals( targetSegs[ c ] ) ) { // we found a difference, note where last = c; break; } } // end for c // fell out the bottom. That means source and target directories are // identical. // up to but not including last. // generate URL to work up the tree to common place final FastCat sb = new FastCat( ( targetSegs.length - last ) * 2 + 2 ); sb.append( getLevelString( sourceSegs.length - last ) ); // work back down toward target for ( int d = last; d < targetSegs.length; d++ ) { sb.append( targetSegs[ d ], "/" ); } // end for d sb.append( targetName ); return sb.toString(); // we do not translate ../index.html to ./ because it screws up if files are loaded from local hard disk. } // /methods /** * Get full url of file on website. * * @param fileBeingDistributed the file having its macros expanded * * @return fully qualified filename with / in names e.g. http://mindprod.com/politics/bushisms.html with caps in * filename exact. */ @SuppressWarnings( { "WeakerAccess" } ) public static String serverQualifiedURLWithSlashes( File fileBeingDistributed ) { // chop of the lead E:/mindprod and prepend http://mindprod.com return "http://" + configuration.getWebsiteDomain() + "/" + uPathName( fileBeingDistributed ); } // /methods /** * Strip   and entities, to allow content to be used in header fields * * @param content to flatten * * @return flattened text */ public static String stripEntities( String content ) { return DeEntifyStrings.deEntifyHTML( content, ' ' ).trim(); } /** * Strip tags, and   but not other entities, to allow content to be used in header fields * * @param content to flatten * * @return flattened text */ public static String stripHTMLTags( String content ) { return DeEntifyStrings.stripHTMLTags( content ).trim(); } // /methods /** * Strip quotes by converting to ' , and Accented letters to unaccented ones. * Don't Use entities to avoid bringing in whole package. * * @param content to flatten * * @return flattened text */ @SuppressWarnings( { "WeakerAccess" } ) public static String stripQuotes( String content ) { final int length = content.length(); // leave as StringBuilder final StringBuilder sb = new StringBuilder( length + 10 ); for ( int i = 0; i < length; i++ ) { final char c = content.charAt( i ); switch ( c ) { case /* Aacute */ '\u00c1': case /* Acirc */ '\u00c2': case /* Agrave */ '\u00c0': case /* Aring */ '\u00c5': case /* Atilde */ '\u00c3': case /* Auml */ '\u00c4': sb.append( 'A' ); break; case /* AElig */ '\u00c6': sb.append( "AE" ); break; case /* Ccedil */ '\u00c7': sb.append( 'C' ); break; case /* Eacute */ '\u00c9': case /* Ecirc */ '\u00ca': case /* Egrave */ '\u00c8': case /* Euml */ '\u00cb': sb.append( 'E' ); break; case /* Iacute */ '\u00cd': case /* Icirc */ '\u00ce': case /* Igrave */ '\u00cc': case /* Iuml */ '\u00cf': sb.append( 'I' ); break; case /* Ntilde */ '\u00d1': sb.append( 'N' ); break; case /* OElig */ '\u0152': sb.append( "OE" ); break; case /* Oacute */ '\u00d3': case /* Ocirc */ '\u00d4': case /* Ograve */ '\u00d2': case /* Oslash */ '\u00d8': case /* Otilde */ '\u00d5': case /* Ouml */ '\u00d6': sb.append( 'O' ); break; case /* Uacute */ '\u00da': case /* Ucirc */ '\u00db': case /* Ugrave */ '\u00d9': case /* Upsilon */ '\u03a5': case /* Uuml */ '\u00dc': sb.append( 'U' ); break; case /* Yacute */ '\u00dd': case /* Yuml */ '\u0178': sb.append( 'Y' ); break; case /* aacute */ '\u00e1': case /* acirc */ '\u00e2': case /* acute */ '\u00b4': case /* agrave */ '\u00e0': case /* aring */ '\u00e5': case /* atilde */ '\u00e3': case /* auml */ '\u00e4': sb.append( 'a' ); break; case /* aelig */ '\u00e6': sb.append( "ae" ); break; case /* ccedil */ '\u00e7': sb.append( 'c' ); break; case /* eacute */ '\u00e9': case /* ecirc */ '\u00ea': case /* egrave */ '\u00e8': case /* euml */ '\u00eb': sb.append( 'e' ); break; case /* emsp */ '\u2003': case /* ensp */ '\u2002': case /* nbsp */ '\u00a0': sb.append( ' ' ); break; case /* iacute */ '\u00ed': case /* icirc */ '\u00ee': case /* igrave */ '\u00ec': case /* iuml */ '\u00ef': sb.append( 'i' ); break; case /* laquo */ '\u00ab': case /* ldquo */ '\u201c': case /* lsaquo */ '\u2039': case /* lsquo */ '\u2018': case /* rdquo */ '\u201d': case /* rsaquo */ '\u203a': case /* rsquo */ '\u2019': case /* sbquo */ '\u201a': case /* quot */ '\u0022': sb.append( '\'' ); break; case /* mdash */ '\u2014': case /* minus */ '\u2212': case /* ndash */ '\u2013': sb.append( '-' ); break; case /* ntilde */ '\u00f1': sb.append( 'n' ); break; case /* oacute */ '\u00f3': case /* ocirc */ '\u00f4': case /* ograve */ '\u00f2': case /* oslash */ '\u00f8': case /* otilde */ '\u00f5': case /* ouml */ '\u00f6': sb.append( 'o' ); break; case /* oelig */ '\u0153': sb.append( "oe" ); break; case /* sup1 */ '\u00b9': sb.append( '1' ); break; case /* sup2 */ '\u00b2': sb.append( '2' ); break; case /* sup3 */ '\u00b3': sb.append( '3' ); break; case /* uacute */ '\u00fa': case /* ucirc */ '\u00fb': case /* ugrave */ '\u00f9': case /* uuml */ '\u00fc': sb.append( 'u' ); break; case /* yacute */ '\u00fd': case /* yuml */ '\u00ff': sb.append( 'y' ); break; case /* hellip */ '\u2026': sb.append( "..." ); break; default: // we don't tidy up every possible non-ASCII char or strange punctuation sb.append( c ); } } Tools.checkStringBuilderEstimate( sb, length - 10, length + 10, null ); return sb.toString(); } // /methods /** * Get local webroot relative name, with backslashes. * * @param uPathFileName file you want as File object * * @return file as a File */ @SuppressWarnings( { "WeakerAccess" } ) public static File toFileFromUPath( String uPathFileName ) { assert uPathFileName != null : "null uPathFileName"; uPathFileName = ST.trimLeading( uPathFileName, '/' ); return new File( rootDir, uPathFileName ); } // /methods /** * Get webroot relative name, with slashes. * * @param fileBeingDistributed the file having its macros expanded * * @return name relative to LOCAL_WEBROOT_WITH_BACKSLASHES with / in names e.g.politics/bushisms.html */ public static String uPathName( File fileBeingDistributed ) { assert fileBeingDistributed != null : "null fileBeingDistributed"; String fullName = qualifiedNameWithBackslashes( fileBeingDistributed ); assert fullName.toLowerCase() .startsWith( rootDirNameWithBackSlashes ) : "not uPath filename [" + fullName + "]"; // chop off webRoot and lead \ return fullName.substring( rootDirNameWithBackSlashes.length() + 1 ).replace( File.separatorChar, '/' ); } // /methods /** * Get webroot relative name, with slashes. webroot file returns "". * * @param fileBeingDistributed the file having its macros expanded * * @return name relative to LOCAL_WEBROOT_WITH_BACKSLASHES with / in names e.g.politics/bushisms.html */ public static String uPathParentWithSlashes( File fileBeingDistributed ) { if ( fileBeingDistributed == null ) { throw new IllegalArgumentException( "getCanonicalFile failed: null fileBeingDistributed" ); } try { final String parent = fileBeingDistributed.getCanonicalFile().getParent(); // root dir -> "" not "/". final int rootLen = rootDirNameWithSlashes.length(); final int parentLen = parent.length(); if ( parentLen == rootLen ) { return ""; } else if ( parentLen > rootLen ) { // chop off webRoot and lead \ return parent.substring( rootDirNameWithSlashes.length() + 1 ).replace( File.separatorChar, '/' ); } else { // parentLen < rootlen throw new IllegalArgumentException( "Tools.uPathParentWithSlashes failed. file=" + fileBeingDistributed.getAbsolutePath() + "\" parent=" + parent + "\"" ); } } catch ( IOException e ) { throw new IllegalArgumentException( "getCanonicalFile failed" ); } } // /methods // /methods }