const CSRI_SEPARATOR_NO_AUTHORITY = ")@no-authority/";
const CSRI_SEPARATOR_OPAQUE_URI = ")@opaque-uri/";
const CSRI_SEPARATORS = [
CSRI_SEPARATOR_NO_AUTHORITY,
CSRI_SEPARATOR_OPAQUE_URI,
")@",
")"
];
/**
* The file path separator on Windows , that is, <code>'\'</code>.
* @type string
*/
const WINDOWS_FILE_PATH_SEPARATOR = '\\';
/**
* The platform specific file path separator, that is, <code>'\'</code>
* on Windows and <code>'/'</code> on the other platforms.
* @type string
*/
const FILE_PATH_SEPARATOR =
PLATFORM_IS_WINDOWS? WINDOWS_FILE_PATH_SEPARATOR : '/';
/**
* Some helper functions (static methods) related to URIs and to file paths.
*/
/*TEST|export|TEST*/ class URIUtil {
// -----------------------------------------------------------------------
// "csri:" server specific URL to normal client URI
// -----------------------------------------------------------------------
/**
* Converts specified URI to an actual URI if it's a "csri:" URL
* <small>(Client Side Resource Identifier, an URL form common to
* {@link XMLEditor} and XXE server of an URI managed by
* client-code)</small>; returns specified URI as is otherwise.
* <p>"csri:" URL examples:
* <pre>csri://(file)@no-authority/tmp/doc.xml
*csri://(file)@myserver/share/docs/doc.xml
*csri://(urn)@opaque-uri/ietf:rfc:2648
*csri://(https)@www.xmlmind.com:80/xmleditor/index.html
*csri://(ftp)foo:bar@xmlmind.com/xmleditor/index.html</pre>
*
* @param {string} url - an URL, possibly a "csri:" one.
* @return {string} corresponding URI.
*/
static csriURLToURI(url) {
if (url === null || !url.startsWith("csri://(")) {
return url;
}
let uri = url;
for (let separ of CSRI_SEPARATORS) {
let pos = uri.indexOf(separ, 8);
if (pos > 8) {
let scheme = uri.substring(8, pos).trim();
if (scheme.length > 0) {
let tail;
if (CSRI_SEPARATOR_NO_AUTHORITY === separ) {
tail = "///" + uri.substring(pos + separ.length);
} else if (CSRI_SEPARATOR_OPAQUE_URI === separ) {
tail = uri.substring(pos + separ.length);
} else {
tail = "//" + uri.substring(pos + separ.length);
}
uri = scheme + ":" + tail;
}
// Done.
break;
}
}
return uri;
}
// -----------------------------------------------------------------------
// URI utilities
// -----------------------------------------------------------------------
/**
* Returns the basename, if any, of specified URI.
*
* @param {string} uri - a relative or absolute, <em>valid</em> URI.
* @return {string} the basename of specified URI <small>(decoded using
* <code>decodeURIComponent</code>)</small> if any;
* <code>null</code> otherwise.
*/
static uriBasename(uri) {
let [scheme, authority, path, query, fragment] = URIUtil.splitURI(uri);
if (!path) {
return null;
}
let basename = URIUtil.pathBasename(path, '/');
return !basename? null : decodeURIComponent(basename);
}
/**
* Changes the basename of specified URI.
*
* @param {string} uri - a relative or absolute, <em>valid</em> URI.
* @param {string} basename - the new basename <small>(to be encoded using
* <code>encodeURIComponent</code>)</small>.
* @return {string} modified URI; <code>null</code> if specified
* URI has no path.
*/
static uriSetBasename(uri, basename) {
let [scheme, authority, path, query, fragment] = URIUtil.splitURI(uri);
if (!path) {
return null;
}
basename = encodeURIComponent(basename);
let parent = URIUtil.pathParent(path, '/', /*trailingSepar*/ true);
if (!parent) {
// Common case: uri consists in just a basename (e.g. "bin.dat").
path = basename;
} else {
path = parent + basename;
}
return URIUtil.joinURI(scheme, authority, path, query, fragment);
}
/**
* Splits specified URI into its components:
* scheme, authority, path, query, fragment.
* <p>Note that returned URI components are <em>NOT</em> decoded using
* <code>decodeURIComponent</code>.
*
* @param {string} uri - a relative or absolute, <em>valid</em> URI.
* @return {array} an array containing scheme, authority, path, query,
* fragment, each of this component possibly being <code>null</code> or
* just the empty string.
*/
static splitURI(uri) {
if (!uri) {
return [ null, null, null, null, null ];
}
// Cannot simply use the URL API.
// The URL API consider any protocol other than "http", "https", "ftp"
// as being opaque.
// For example, (new URL(uri)).pathname for "foo://bar.com/gee/wiz.dat"
// would be "//bar.com/gee/wiz.dat".
let scheme = null;
let authority = null;
let path = null;
let query = null;
let fragment = null;
// Authority?
let pos = uri.indexOf("://");
if (pos < 0) {
// No authority. Example: "file:/tmp/server.log".
pos = uri.indexOf(":");
if (pos < 0) {
// No scheme. Example: "../foo/bar.txt".
path = uri;
} else {
// Example: "urn:isbn:8088451".
scheme = uri.substring(0, pos);
path = uri.substring(pos+1);
}
} else {
// Example: "ftp://john@acme.com/foo/bar.txt".
scheme = uri.substring(0, uri.indexOf(":"));
pos += 3; // Skip "://".
let slash = uri.indexOf("/", pos);
if (slash < 0) {
authority = uri.substring(pos);
// Example: "http://acme.com". No path, but "" and not null
// for consistency with the other cases.
path = "";
} else {
authority = uri.substring(pos, slash);
// Path starting with "/".
path = uri.substring(slash);
}
}
// Fragment?
if (path !== null) {
pos = path.lastIndexOf("#");
if (pos >= 0) {
fragment = path.substring(pos+1);
path = path.substring(0, pos);
}
}
// Query?
if (path !== null) {
pos = path.lastIndexOf("?");
if (pos >= 0) {
query = path.substring(pos+1);
path = path.substring(0, pos);
}
}
return [ scheme, authority, path, query, fragment ];
}
/**
* Returns the file extension, if any, of specified URI.
*
* @param {string} uri - a relative or absolute, <em>valid</em> URI.
* @return {string} the file extension of specified URI if any;
* <code>null</code> otherwise.
*/
static uriExtension(uri) {
let [scheme, authority, path, query, fragment] = URIUtil.splitURI(uri);
if (!path) {
return null;
}
let ext = URIUtil.pathExtension(path, '/');
return !ext? null : decodeURIComponent(ext);
}
/**
* Returns the parent, if any, of specified URI.
*
* @param {string} uri - a relative or absolute, <em>valid</em> URI.
* @param {string} [trailingSepar=true] trailingSepar - if
* <code>true</code>, returned URI has a path which ends with
* a trailing <code>'/'</code> character.
* @return {string} the parent of specified URI if any;
* <code>null</code> otherwise.
*/
static uriParent(uri, trailingSepar=true) {
let [scheme, authority, path, query, fragment] = URIUtil.splitURI(uri);
if (!path) {
return null;
}
let parent = URIUtil.pathParent(path, '/', trailingSepar);
if (!parent) {
return null;
}
return URIUtil.joinURI(scheme, authority, parent);
}
/**
* Join specified URI components and return resulting URI.
* <p>Specified URI components are joined as is, that is,
* without first encoding them using <code>encodeURIComponent</code>.
*/
static joinURI(scheme, authority, path, query=null, fragment=null) {
let uri = "";
if (scheme) {
uri += scheme;
uri += ':';
}
if (authority) {
uri += "//";
uri += authority;
}
if (path) {
uri += path;
}
if (query) {
uri += '?';
uri += query;
}
if (fragment) {
uri += '#';
uri += fragment;
}
return uri;
}
/**
* Tests whether specified URI is an absolute hierarchical URI.
*
* @param {string} uri - a relative or absolute, <em>valid</em> URI.
* @return {boolean} <code>true</code> if it's an absolute
* hierarchical URI; <code>false</code> otherwise.
*/
static isAbsoluteURI(uri) {
let [scheme, authority, path, query, fragment] = URIUtil.splitURI(uri);
return (scheme && path && path.startsWith('/'))? true : false;
}
/**
* Returns specified URI after making it relative to the other URI
* (when this is possible).
*
* @param {string} uri - an absolute, <em>valid</em> URI.
* @param {string} baseURI - another absolute, <em>valid</em> URI
* which is used as a base URI.
* @return {string} the relative URI when possible;
* specified uri as is otherwise (for example, when specified URIs
* don't have the same scheme or authority).
*/
static relativizeURI(uri, baseURI) {
// uri and baseURI must be absolute URIs having the same scheme and
// authority (case-insensitive).
let [scheme1, authority1, path1, query1, fragment1] =
URIUtil.splitURI(uri);
if (scheme1) {
scheme1 = scheme1.toLowerCase();
}
let auth1 = authority1;
if (!auth1) {
auth1 = null;
} else {
auth1 = auth1.toLowerCase();
if (scheme1 === "file" && auth1 === "localhost") {
auth1 = null;
}
}
let [scheme2, authority2, path2, query2, fragment2] =
URIUtil.splitURI(baseURI);
if (scheme2) {
scheme2 = scheme2.toLowerCase();
}
let auth2 = authority2;
if (!auth2) {
auth2 = null;
} else {
auth2 = auth2.toLowerCase();
if (scheme2 === "file" && auth2 === "localhost") {
auth2 = null;
}
}
if (!scheme1 || !path1 || !path1.startsWith('/') ||
!scheme2 || !path2 || !path2.startsWith('/') ||
scheme1 !== scheme2 || auth1 !== auth2) {
return uri;
}
// ---
if (PLATFORM_IS_WINDOWS && scheme1 === "file") {
// On Windows, "file:" URLs may have different volumes (e.g.
// file:/C:/foo and file:/D:/bar). When this is the case, do not
// attempt to compute a relative path.
let drive1 = null;
let match = path1.match(/^\/([a-zA-Z]:)\//);
if (match !== null) {
drive1 = match[1].toUpperCase(); // Not case-sensitive.
// Normalize to upper-case.
}
let drive2 = null;
match = path2.match(/^\/([a-zA-Z]:)\//);
if (match !== null) {
drive2 = match[1].toUpperCase();
}
if (drive1 !== drive2) {
return uri;
}
}
let relPath = URIUtil.relativizePath(path1, path2, '/');
return URIUtil.joinURI(/*scheme*/ null, /*authority*/ null,
relPath, query1, fragment1);
}
/**
* Returns specified relative URI after resolving it against the other URI.
*
* @param {string} uri - a relative or absolute, <em>valid</em> URI.
* @param {string} baseURI - an absolute, <em>valid</em> URI
* which is used as a base URI.
* @return {string} the resolved URI when possible;
* specified uri as is otherwise (for example, when specified URI
* is already absolute).
*/
static resolveURI(uri, baseURI) {
// baseURI must be an absolute URI.
let [scheme2, authority2, path2, query2, fragment2] =
URIUtil.splitURI(baseURI);
if (!scheme2 || !path2 || !path2.startsWith('/')) {
return uri;
}
let [scheme1, authority1, path1, query1, fragment1] =
URIUtil.splitURI(uri);
if (scheme1 && path1 && path1.startsWith('/')) {
// Already absolute: nothing to do.
return uri;
}
let absPath = !path1? "/" : URIUtil.resolvePath(path1, path2, '/');
return URIUtil.joinURI(scheme2, authority2,
absPath, query1, fragment1);
}
/**
* Returns specified absolute URI after normalizing its authority
* if it's a "file:" URI. Otherwise returns specified URI as is.
*/
static normalizeFileURI(uri) {
let uri2;
if (uri && (uri2 = uri.toLowerCase()).startsWith("file:/")) {
let matches = (new RegExp("^file://([^/]*)/")).exec(uri2);
if (matches === null) {
// file:/PATH.
uri = "file:///" + uri.substring(6);
} else {
if (matches[1] === "localhost") {
// file://localhost/PATH.
uri = "file:///" + uri.substring(17);
}
// Otherwise, file:///PATH or file://AUTH/PATH. Leave it as is.
}
}
return uri;
}
// -----------------------------------------------------------------------
// URI to platform specific file path and the other way round
// -----------------------------------------------------------------------
/**
* Returns the file path corresponding to specified absolute "file:" URI.
*
* @param {string} uri - an absolute, <em>valid</em>, "file:" URI.
* @param {string} [pathSepar=FILE_PATH_SEPARATOR] pathSepar -
* the character used to separate file path segments
* (that is, '\' on Windows).
* @return {string} corresponding absolute file path when possible,
* <code>null</code> otherwise.
*/
static uriToFilePath(uri, pathSepar=FILE_PATH_SEPARATOR) {
let [scheme, authority, path, query, fragment] = URIUtil.splitURI(uri);
if (!scheme || scheme !== "file") {
return null;
}
if (!path) {
path = "/";
}
// Ignore authority, query and fragment.
let segments = path.split('/');
for (let i = segments.length-1; i >= 0; --i) {
segments[i] = decodeURIComponent(segments[i]);
}
let file = segments.join(pathSepar);
if (pathSepar === WINDOWS_FILE_PATH_SEPARATOR) {
if (authority && authority !== "localhost") {
// Special processing of "file://server/path".
file = "\\\\" + authority + file;
} else {
// "\C:" becomes "C:\".
// "\C:\temp\foo" becomes "C:\temp\foo".
if (file.match(/^\\[a-zA-Z]:/)) {
if (file.length === 3) {
file = file.substring(1) + "\\";
} else if (file.charAt(3) === "\\") {
file = file.substring(1);
}
}
}
}
return URIUtil.normalizePath(file, pathSepar);
}
/**
* Convert specified path to a "file:" URI.
*
* @param {string} path - relative or absolute path.
* @param {string} [pathSepar=FILE_PATH_SEPARATOR] pathSepar -
* the character used to separate file path segments
* (that is, '\' on Windows).
* @return {string} corresponding relative or absolute "file:" URI.
* Note that if <code>path</code> is relative, this URI will have no
* "file:" scheme (e.g. returns "tmp/my%20doc.xml" for "tmp\\my doc.xml");
*/
static pathToFileURI(path, pathSepar=FILE_PATH_SEPARATOR) {
let [drive, checkedPath] = URIUtil.checkPath(path, pathSepar);
let uri;
if (URIUtil.testIsAbsolute(drive, checkedPath, pathSepar)) {
uri = "file://";
if (drive !== null) {
if (drive.match(/^[a-zA-Z]:/)) {
uri += "/" + drive;
} else {
// UNC path like "\\serv\share\doc.xml".
// Discard leading "\\".
uri += drive.substring(2);
}
// uri is something like "file:///C:" or "file://my-server".
}
} else {
// Relative path. Resulting uri will have no "file:" scheme.
uri = "";
}
let parts = checkedPath.split(pathSepar).map((part) => {
if (part.length > 0) {
part = encodeURIComponent(part);
}
return part;
});
uri += parts.join('/');
return uri;
}
static checkPath(path, pathSepar) {
let drive = null;
if (pathSepar === WINDOWS_FILE_PATH_SEPARATOR) {
// Example: \\ComputerName\SharedFolder\Resource
let match = path.match(/^(\\\\[^\\]+)\\/);
if (match !== null) {
drive = match[1].toUpperCase(); // Not case-sensitive.
// Normalize to upper-case.
path = path.substring(drive.length);
} else {
// Example: C:\Folder\File
// Drive relative paths like D:File not supported.
let match = path.match(/^([a-zA-Z]:)\\/);
if (match !== null) {
drive = match[1].toUpperCase(); // Not case-sensitive.
// Normalize to upper-case.
path = path.substring(drive.length);
}
}
}
// Otherwise, we have an absolute or relative path.
path = URIUtil.doNormalizePath(path, pathSepar);
return [drive, path];
}
static doNormalizePath(path, pathSepar) {
const originalPath = path;
const prependSlash = path.startsWith(pathSepar);
const appendSlash = path.endsWith(pathSepar);
if (prependSlash || appendSlash) {
let start = 0;
let end = path.length-1;
while (start <= end && path.charAt(start) === pathSepar) {
++start;
}
while (end >= start && path.charAt(end) === pathSepar) {
--end;
}
if (start > end) {
return pathSepar;
}
path = path.substring(start, end+1);
// This one does not start or end with pathSepar.
}
// ---
let twoPasses = false;
let buffer = "";
let parts = path.split(pathSepar);
for (let part of parts) {
if (part.length === 0 ||
"." === part) { // e.g. "x//y", "x/./y"
continue;
}
if (part.trim().length === 0) {
// Malformed path. Give up.
return originalPath;
}
if (buffer.length > 0) {
buffer += pathSepar;
}
buffer += part;
if (".." === part) {
twoPasses = true;
}
}
path = buffer;
// ---
if (twoPasses) {
parts = path.split(pathSepar);
buffer = "";
for (let i = parts.length-1; i >= 0; --i) {
let part = parts[i];
if (".." === part) {
// Count skipped levels: (i-j).
let j = i;
while (j >= 0 && ".." === parts[j]) {
--j;
}
// Skip the ".." and the corresponding level: 2*(i-j).
// (+1 because the for loop includes a --i.)
i = i - 2*(i-j) + 1;
if (i < 0) {
// Malformed path. Give up.
return originalPath;
}
} else {
if (buffer.length > 0) {
buffer = pathSepar + buffer;
}
buffer = part + buffer;
}
}
}
path = buffer;
// ---
if (prependSlash) {
path = pathSepar + path;
}
if (appendSlash) {
path = path + pathSepar;
}
return path;
}
static testIsAbsolute(drive, checkedPath, pathSepar) {
if (pathSepar === WINDOWS_FILE_PATH_SEPARATOR && drive === null) {
return false;
}
return checkedPath.startsWith(pathSepar);
}
// -----------------------------------------------------------------------
// Low-level path utilities.
// (Work for platform specific file and URI paths.
// Work for relative and absolute paths.)
// -----------------------------------------------------------------------
/**
* Tests whether specified path is absolute or relative.
* <p>On Windows, a drive relative path like "\foo\bar" is considered
* to be relative. Only "C:\foo\bar" or "\\my-server\foo\bar" are
* considered to be absolute.
*
* @param {string} path - relative or absolute path.
* @param {string} pathSepar - the character used to separate
* path segments (that is, '/' for URIs).
* @return {boolean} <code>true</code> if absolute;
* <code>false</code> if relative.
*/
static isAbsolutePath(path, pathSepar) {
let [drive, checkedPath] = URIUtil.checkPath(path, pathSepar);
return URIUtil.testIsAbsolute(drive, checkedPath, pathSepar);
}
/**
* Resolves specified path against specified base.
* <p><strong>IMPORTANT:</strong> a directory path is expected to end
* with <code><i>pathSepar</i></code>.
*
* @param {string} path - relative or absolute path.
* @param {string} basePath - relative or absolute path.
* @param {string} pathSepar - the character used to separate
* path segments (that is, '/' for URIs).
* @return {string} resolved path.
*/
static resolvePath(path, basePath, pathSepar) {
if (URIUtil.isAbsolutePath(path, pathSepar) ||
!basePath) {
return path;
}
let parentPath;
if (basePath.endsWith(pathSepar)) {
// Assume basePath points to a directory.
parentPath = basePath;
} else {
parentPath = URIUtil.pathParent(basePath, pathSepar,
/*trailingSepar*/ true);
}
return URIUtil.normalizePath(!parentPath? path : parentPath + path,
pathSepar);
}
/**
* Returns specified relative or absolute path after normalizing it,
* that is, after removing ".", ".." and duplicate path separators from it.
*
* @param {string} path - relative or absolute path.
* @param {string} pathSepar - the character used to separate
* path segments (that is, '/' for URIs).
* @return {string} normalized path or original path if it is not possible
* to normalize it (examples: "../../foo/bar", "a/ /b/c").
*/
static normalizePath(path, pathSepar) {
let [drive, checkedPath] = URIUtil.checkPath(path, pathSepar);
if (drive !== null) {
checkedPath = drive + checkedPath;
}
return checkedPath;
}
/**
* Returns the extension of specified path. To make it simple, the
* substring after last '.', not including last '.'.
* <ul>
* <li>Returns <code>null</code> for "<tt>/tmp/test</tt>".
* <li>Returns the empty string for "<tt>/tmp/test.</tt>".
* <li>Returns <code>null</code> for "<tt>~/.profile</tt>".
* <li>Returns "<tt>gz</tt>" for "<tt>/tmp/test.tar.gz</tt>".
* </ul>
*
* @param {string} path - relative or absolute path possibly
* having an extension
* @param {string} pathSepar - the character used to separate
* path segments (that is, '/' for URIs)
* @return {string} extension if any; <code>null</code> otherwise
*/
static pathExtension(path, pathSepar) {
let [drive, checkedPath] = URIUtil.checkPath(path, pathSepar);
let dot = URIUtil.indexOfDot(checkedPath, pathSepar);
if (dot < 0) {
return null;
} else {
return checkedPath.substring(dot+1);
}
}
static indexOfDot(checkedPath, pathSepar) {
let dot = checkedPath.lastIndexOf('.');
if (dot >= 0) {
let baseNameStart = checkedPath.lastIndexOf(pathSepar);
if (baseNameStart < 0) {
baseNameStart = 0;
} else {
++baseNameStart;
}
if (dot <= baseNameStart) {
dot = -1;
}
}
return dot;
}
/**
* Returns the base name of specified path. To make it simple, the
* substring after last '/'.
* <p>Examples:
* <ul>
* <li>Returns the empty string for "<tt>/</tt>".
* <li>Returns "<tt>foo</tt>" for "<tt>foo</tt>".
* <li>Returns "<tt>bar</tt>" for "<tt>/foo/bar</tt>".
* <li>Returns "<tt>bar</tt>" for "<tt>/foo/bar/</tt>".
* </ul>
*
* @param {string} path - relative or absolute path
* @param {string} pathSepar - the character used to separate
* path segments (that is, '/' for URIs)
* @return {string} base name of specified path
*/
static pathBasename(path, pathSepar) {
let [drive, checkedPath] = URIUtil.checkPath(path, pathSepar);
if (checkedPath === pathSepar) {
return "";
}
// Normalize "/foo/bar/" to "/foo/bar".
if (checkedPath.endsWith(pathSepar)) {
checkedPath = checkedPath.substring(0, checkedPath.length-1);
}
let slash = checkedPath.lastIndexOf(pathSepar);
if (slash < 0) {
return checkedPath;
}
return checkedPath.substring(slash+1);
}
/**
* Returns the parent of specified path. To make it simple,
* the substring before last '/'.
* <p>Examples:
* <ul>
* <li>Returns <code>null</code> for "<tt>/</tt>".
* <li>Returns <code>null</code> for "<tt>foo</tt>".
* <li>Returns "<tt>/</tt>" for "<tt>/foo</tt>".
* <li>Returns "<tt>/foo</tt>" (or "<tt>/foo/</tt>") for
* "<tt>/foo/bar</tt>".
* <li>Returns "<tt>/foo</tt>" (or "<tt>/foo/</tt>") for
* "<tt>/foo/bar/</tt>".
* </ul>
*
* @param {string} path - relative or absolute path
* @param {string} pathSepar - the character used to separate
* path segments (that is, '/' for URIs)
* @param {string} trailingSepar - if <code>true</code>,
* returned path ends with a trailing <code>pathSepar</code> character
* @return {string} parent of specified path or
* <code>null</code> for <code>pathSepar</code> or if specified path
* does not contain the <code>pathSepar</code> character
*/
static pathParent(path, pathSepar, trailingSepar) {
let [drive, checkedPath] = URIUtil.checkPath(path, pathSepar);
if (checkedPath === pathSepar) {
return null;
}
// Normalize "/foo/bar/" to "/foo/bar".
if (checkedPath.endsWith(pathSepar)) {
checkedPath = checkedPath.substring(0, checkedPath.length-1);
}
let slash = checkedPath.lastIndexOf(pathSepar);
if (slash < 0) {
return null;
}
let parent;
if (slash === 0) {
// Example: "/foo"
parent = pathSepar;
} else {
parent = checkedPath.substring(0, trailingSepar? slash+1 : slash);
}
if (drive !== null) {
parent = drive + parent;
}
return parent;
}
/**
* Returns specified path after making it relative to the other path
* (when this is possible).
* <p><strong>IMPORTANT:</strong> a directory path is expected to end
* with <code><i>pathSepar</i></code>.
*
* @param {string} path - an absolute path.
* @param {string} basePath - another absolute path which is used as a base.
* @param {string} pathSepar - the character used to separate
* path segments (that is, '/' for URIs)
* @return {string} the relative path when possible;
* specified path as is otherwise (Windows example: when specified paths
* don't have the same drive.
*/
static relativizePath(path, basePath, pathSepar) {
// path and basePath must be absolute paths having the same drive (if
// any).
let [drive1, checkedPath1] = URIUtil.checkPath(path, pathSepar);
let [drive2, checkedPath2] = URIUtil.checkPath(basePath, pathSepar);
if (!URIUtil.testIsAbsolute(drive1, checkedPath1, pathSepar) ||
!URIUtil.testIsAbsolute(drive2, checkedPath2, pathSepar) ||
drive1 !== drive2) {
return path;
}
path = checkedPath1;
basePath = checkedPath2;
if (pathSepar === path) {
return pathSepar;
}
if (pathSepar === basePath) {
return path.substring(1);
}
if (!basePath.endsWith(pathSepar)) {
basePath = URIUtil.pathParent(basePath, pathSepar,
/*trailingSepar*/ true);
}
// Windows filenames are case insensitive.
const onWindows = (pathSepar === WINDOWS_FILE_PATH_SEPARATOR);
const upSegment = ".." + pathSepar;
let relPath = "";
while (basePath !== null) {
let start = basePath;
if (path.startsWith(start) ||
(onWindows &&
path.toLowerCase().startsWith(start.toLowerCase()))) {
relPath += path.substring(start.length);
break;
}
relPath += upSegment;
basePath = URIUtil.pathParent(basePath, pathSepar,
/*trailingSepar*/ true);
}
return relPath;
}
// -----------------------------------------------------------------------
static formatFileSize(byteCount, fallback=null) {
if (!Number.isInteger(byteCount) || byteCount < 0) {
return fallback;
}
let unit = 0;
while (byteCount >= 1024) {
byteCount /= 1024;
unit++;
}
if (unit > 0) {
byteCount = byteCount.toFixed(1);
}
return `${byteCount} ${" KMGTPEZY"[unit]}B`;
}
static formatFileDate(millis, fallback=null) {
if (!Number.isInteger(millis) || millis < 0) {
return fallback;
}
const date = new Date(millis);
let text = date.getFullYear();
text += "-";
text += URIUtil.formatFileDateField(1 + date.getMonth());
text += "-";
text += URIUtil.formatFileDateField(date.getDate());
text += " ";
text += URIUtil.formatFileDateField(date.getHours());
text += ":";
text += URIUtil.formatFileDateField(date.getMinutes());
return text;
}
static formatFileDateField(value) {
let text = String(value);
while (text.length < 2) {
text = "0" + text;
}
return text;
}
// -----------------------------------------------------------------------
/*TEST|
static test_URIUtil(logger, filePathSepar) {
const uriList = [
"",
"foo/bar.dat",
"/foo/bar.dat",
"file:/foo/bar.xml",
"file:///foo/bar.xml",
"file:/Z:/foo/bar.xml#end",
"file:/C:",
"file:///C:/foo/././gee//wiz//bar.xml",
"file://GOOD/foo/gee/wiz/../../bar.xml",
"file://BAD/foo/gee/wiz/../../../../bar.xml",
"file://BAD/foo/%20%20%20%20/bar.xml",
"http://www.uiuc.edu:80",
"http://www.uiuc.edu:80/",
"http://www.uiuc.edu:80/SDG/",
"http://www.uiuc.edu:80/SDG/Software/Mosaic/Demo/url-primer",
"http://www.uiuc.edu:80/SDG/Software/Mosaic/Demo/.url-primer",
"http://www.uiuc.edu:80/SDG/Software/Mosaic/Demo/url-primer.html",
"http://www.uiuc.edu/SDG/Software/Mosaic/Demo/url-primer.html.gz",
"http://127.0.0.1/SDG/Software/Mosaic/Demo/url-primer.html#chapter1",
"foo://example.com:8042/over/there?name=ferret#nose",
"urn:example:animal:ferret:nose",
"urn:example:animal:ferret:nose?name=ferret#foo",
"ftp://hs%40x:my%20pass@www.foo.com:1024/bar/\
vin%20ros%C3%A9.jar#ros%C3%A9",
"csri://(file)@no-authority/",
"csri://(file)@no-authority/home/hussein/",
"csri://(file)@no-authority/home//hussein/./tmp/..////",
"csri://(file)@no-authority/../.././doc.xml",
"csri://(file)@no-authority/z:/docs/doc.xml",
"csri://(file)@myserver/share/docs/doc.xml",
"csri://(file)@192.168.1.205/tmp/dump.dat",
"csri://(file)@[1080:0:0:0:8:800:200C:417A]:80/tmp/dump.dat",
"csri://(xdocs)@MyServer/Content/sample/dita/tasks/\
Very%20Important.dita",
"csri://(https)@www.xmlmind.com/xmleditor/index.html#notice",
"csri://(http)@www.xmlmind.com:80/xslsrv/exec?op=jobs&auth=toto",
"csri://(ftp)john:doe@localhost/src/test/makefile",
"csri://(urn)@opaque-uri/ietf:rfc:2648",
"csri://(urn)@opaque-uri/lex:eu:council:directive:\
2010-03-09;2010-19-UE#summary",
];
for (let uri of uriList) {
let [scheme, authority, path, query, fragment] =
URIUtil.splitURI(uri);
let abs = URIUtil.isAbsoluteURI(uri);
let name = URIUtil.uriBasename(uri);
let ext = URIUtil.uriExtension(uri);
let parent = URIUtil.uriParent(uri);
// File path tests ---
let uri2 = URIUtil.csriURLToURI(uri);
let file = URIUtil.uriToFilePath(uri2, filePathSepar);
let uri3 = null;
if (file !== null) {
uri3 = URIUtil.pathToFileURI(file, filePathSepar);
}
logger(`uri='${uri}':\n\
splitURI=[scheme=${scheme}, authority=${authority}, path=${path}, \
query=${query}, fragment=${fragment}],\n\
abs=${abs}, basename='${name}', extension='${ext}', parent='${parent}'\n\
csriURLToURI(uri)=uri2=${uri2}\n\
uriToFilePath(uri2,${filePathSepar})=${file}\n\
pathToFileURI(file,${filePathSepar})=${uri3}
---`);
}
}
|TEST*/
// -----------------------------------------------------------------------
/*TEST|
static test_relativize(logger) {
const paths1 = [
"", "",
"/", "/",
"/", "/foo",
"/foo", "/",
"/foo", "/bar",
"foo", "/bar",
"/foo", "bar",
"/home/hussein", "/home",
"/home/hussein", "/home/",
"/foo/gee", "/foo/gee/wiz.xml",
"/foo/zip/bar.xml", "/foo/gee/wiz.xml",
"/foo/zip/bar.xml", "/foo/wiz.xml",
"/foo/bar.xml", "/foo/gee/wiz.xml",
"/foo/zip/bar.xml", "/foo/zip/bar.xml",
"/usr/local/bin/html2ps", "/usr/bin/grep",
"/foo/zip/bar/", "/foo/wiz",
"/foo/bar", "/foo/gee/wiz/",
"/foo/zip/bar/", "/foo/wiz/",
"/foo/bar/", "/foo/gee/wiz/",
"/foo/bar/", "/foo/bar/",
"/foo/bar/", "/foo/bar/gee",
"/foo/bar/gee", "/foo/bar/",
"/foo/bar/", "/foo/bar/wiz/",
"/foo/bar/wiz/", "/foo/bar/",
];
URIUtil.testRelativizePath(logger, paths1, '/');
logger("---");
const paths2 = [
"Z:", "Z:",
"C:\\", "C:\\",
"C:\\home\\hussein", "c:\\home",
"C:\\home\\hussein", "c:\\home\\",
"C:\\", "C:\\home\\hussein",
"C:\\home\\hussein", "C:\\",
"C:\\home", "c:\\home\\hussein",
"c:\\home\\hussein", "C:\\HOME",
"C:\\HOME\\HUSSEIN", "c:\\home",
"C:\\home\\hussein\\DOT emacs", "C:\\home\\hussein\\bin\\clean.bat",
"C:\\HOME\\HUSSEIN\\BIN\\CLEAN.BAT", "c:\\home\\hussein\\DOT emacs",
"C:\\home\\hussein\\DOT emacs", "C:\\home\\hussein\\bin\\",
"C:\\home\\hussein\\bin\\", "C:\\home\\hussein\\DOT emacs",
"C:\\home\\hussein\\bin", "Z:\\docsrc",
"Z:\\bin\\clean", "C:\\home\\hussein\\bin\\clean.bat",
"\\\\Vboxsrv\\home_hussein\\bin\\clean",
"\\\\Vboxsrv\\home_hussein\\.profile",
"\\\\Vboxsrv\\home_hussein\\.profile",
"\\\\VBOXSRV\\HOME_HUSSEIN\\BIN\\CLEAN",
];
URIUtil.testRelativizePath(logger, paths2, '\\');
logger("---");
const uris = [
"file:/Users/john/docs/doc.xml",
"file:/Users/john/",
"file:/Users/john/docs/doc.xml",
"file:///Users/john/",
"file:/Users/john/docs/doc.xml",
"file://localhost/Users/john/",
"file:/C:/Users/john/docs/doc.xml",
"file:/Z:/docs/",
"http://www.acme.com/docs/toc.html",
"https://www.acme.com/docs/",
"http://www.acme.biz/docs/toc.html",
"http://www.acme.com/docs/",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/docs/",
"http://www.acme.com/",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/index.html",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/docs/index.html#index",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/docs/images/logo?format=SVG",
"http://www.acme.com/docs/toc.html",
"http://www.acme.com/index.html",
"http://www.acme.com/docs/chapters/",
"http://www.acme.com/docs/chapters/chapter1.html",
"http://www.acme.com/docs/toc.html",
];
for (let i = 0; i < uris.length; i += 2) {
let uri = uris[i];
let baseURI = uris[i+1];
let relURI = URIUtil.relativizeURI(uri, baseURI);
let absURI = URIUtil.resolveURI(relURI, baseURI);
logger(`"${uri}" relative to "${baseURI}" = "${relURI}"\n\
relative uri resolved = "${absURI}"`);
}
}
static testRelativizePath(logger, paths, pathSepar) {
for (let i = 0; i < paths.length; i += 2) {
let path = paths[i];
let basePath = paths[i+1];
let relPath = URIUtil.relativizePath(path, basePath, pathSepar);
let absPath = URIUtil.resolvePath(relPath, basePath, pathSepar);
logger(`"${path}" relative to "${basePath}" = "${relPath}"\n\
relative path resolved = "${absPath}"`);
}
}
|TEST*/
}