- 1 :
/**
- 2 :
* A helper for working with the Voyant Notebook app.
- 3 :
* @memberof Spyral
- 4 :
* @hideconstructor
- 5 :
*/
- 6 :
class Util {
- 7 :
- 8 :
/**
- 9 :
* Generates a random ID of the specified length.
- 10 :
* @param {Number} len The length of the ID to generate?
- 11 :
* @returns {String}
- 12 :
* @static
- 13 :
*/
- 14 :
static id(len = 8) {
- 15 :
// based on https://stackoverflow.com/a/13403498
- 16 :
const times = Math.ceil(len / 11);
- 17 :
let id = '';
- 18 :
for (let i = 0; i < times; i++) {
- 19 :
id += Math.random().toString(36).substring(2); // the result of this is 11 characters long
- 20 :
}
- 21 :
const letters = 'abcdefghijklmnopqrstuvwxyz';
- 22 :
id = letters[Math.floor(Math.random()*26)] + id; // ensure the id starts with a letter
- 23 :
return id.substring(0, len);
- 24 :
}
- 25 :
- 26 :
/**
- 27 :
*
- 28 :
* @param {Array|Object|String} contents
- 29 :
* @returns {String}
- 30 :
* @static
- 31 :
*/
- 32 :
static toString(contents) {
- 33 :
if (contents.constructor === Array || contents.constructor===Object) {
- 34 :
contents = JSON.stringify(contents);
- 35 :
if (contents.length>500) {
- 36 :
contents = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'+contents.substring(0,500)+' <a href="">+</a><div style="display: none">'+contents.substring(501)+'</div>';
- 37 :
}
- 38 :
}
- 39 :
return contents.toString();
- 40 :
}
- 41 :
- 42 :
/**
- 43 :
*
- 44 :
* @param {String} before
- 45 :
* @param {String} more
- 46 :
* @param {String} after
- 47 :
* @static
- 48 :
*/
- 49 :
static more(before, more, after) {
- 50 :
return before + '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'+more.substring(0,500)+' <a href="">+</a><div style="display: none">'+more.substring(501)+'</div>' + after;
- 51 :
}
- 52 :
- 53 :
- 54 :
/**
- 55 :
* Take a data URL and convert it to a Blob.
- 56 :
* @param {String} dataUrl
- 57 :
* @returns {Blob}
- 58 :
* @static
- 59 :
*/
- 60 :
static dataUrlToBlob(dataUrl) {
- 61 :
const parts = dataUrl.split(',');
- 62 :
const byteString = atob(parts[1]);
- 63 :
const mimeString = parts[0].split(':')[1].split(';')[0];
- 64 :
- 65 :
const ab = new ArrayBuffer(byteString.length);
- 66 :
const ia = new Uint8Array(ab);
- 67 :
for (let i = 0; i < byteString.length; i++) {
- 68 :
ia[i] = byteString.charCodeAt(i);
- 69 :
}
- 70 :
- 71 :
return new Blob([ab], {type: mimeString});
- 72 :
}
- 73 :
- 74 :
/**
- 75 :
* Take a Blob and convert it to a data URL.
- 76 :
* @param {Blob} blob
- 77 :
* @returns {Promise<String>} a Promise for a data URL
- 78 :
* @static
- 79 :
*/
- 80 :
static blobToDataUrl(blob) {
- 81 :
return new Promise((resolve, reject) => {
- 82 :
const fr = new FileReader();
- 83 :
fr.onload = function(e) {
- 84 :
resolve(e.target.result);
- 85 :
};
- 86 :
- 87 :
try {
- 88 :
fr.readAsDataURL(blob);
- 89 :
} catch(e) {
- 90 :
reject(e);
- 91 :
}
- 92 :
});
- 93 :
}
- 94 :
- 95 :
/**
- 96 :
* Take a Blob and convert it to a String.
- 97 :
* @param {Blob} blob
- 98 :
* @returns {Promise<String>} a Promise for a String
- 99 :
* @static
- 100 :
*/
- 101 :
static blobToString(blob) {
- 102 :
return new Promise((resolve, reject) => {
- 103 :
const reader = new FileReader();
- 104 :
reader.addEventListener('loadend', function(ev) {
- 105 :
try {
- 106 :
const td = new TextDecoder();
- 107 :
const data = td.decode(ev.target.result);
- 108 :
resolve(data);
- 109 :
} catch (err) {
- 110 :
reject(err);
- 111 :
}
- 112 :
});
- 113 :
reader.readAsArrayBuffer(blob);
- 114 :
});
- 115 :
}
- 116 :
- 117 :
/**
- 118 :
* Takes an XML document and XSL stylesheet and returns the resulting transformation.
- 119 :
* @param {(Document|String)} xmlDoc The XML document to transform
- 120 :
* @param {(Document|String)} xslStylesheet The XSL to use for the transformation
- 121 :
* @param {Boolean} [returnDoc=false] True to return a Document, false to return a DocumentFragment
- 122 :
* @returns {Document}
- 123 :
* @static
- 124 :
*/
- 125 :
static transformXml(xmlDoc, xslStylesheet, returnDoc=false) {
- 126 :
if (this.isString(xmlDoc)) {
- 127 :
const parser = new DOMParser();
- 128 :
xmlDoc = parser.parseFromString(xmlDoc, 'application/xml');
- 129 :
const error = this._getParserError(xmlDoc);
- 130 :
if (error) {
- 131 :
throw error;
- 132 :
}
- 133 :
}
- 134 :
if (this.isString(xslStylesheet)) {
- 135 :
const parser = new DOMParser();
- 136 :
xslStylesheet = parser.parseFromString(xslStylesheet, 'application/xml');
- 137 :
const error = this._getParserError(xslStylesheet);
- 138 :
if (error) {
- 139 :
throw error;
- 140 :
}
- 141 :
}
- 142 :
const xslRoot = xslStylesheet.firstElementChild;
- 143 :
if (xslRoot.hasAttribute('version') === false) {
- 144 :
// Transform fails in Firefox if version is missing, so return a more helpful error message instead of the default.
- 145 :
throw new Error('XSL stylesheet is missing version attribute.');
- 146 :
}
- 147 :
- 148 :
const xsltProcessor = new XSLTProcessor();
- 149 :
try {
- 150 :
xsltProcessor.importStylesheet(xslStylesheet);
- 151 :
} catch (e) {
- 152 :
console.warn(e);
- 153 :
}
- 154 :
let result;
- 155 :
if (returnDoc) {
- 156 :
result = xsltProcessor.transformToDocument(xmlDoc);
- 157 :
} else {
- 158 :
result = xsltProcessor.transformToFragment(xmlDoc, document);
- 159 :
}
- 160 :
return result;
- 161 :
}
- 162 :
- 163 :
/**
- 164 :
* Checks the Document for a parser error and returns an Error if found, or null.
- 165 :
* @ignore
- 166 :
* @param {Document} doc
- 167 :
* @param {Boolean} [includePosition=false] True to include the error position information
- 168 :
* @returns {Error|null}
- 169 :
* @static
- 170 :
*/
- 171 :
static _getParserError(doc, includePosition=false) {
- 172 :
// fairly naive check for parsererror, consider something like https://stackoverflow.com/a/55756548
- 173 :
const parsererror = doc.querySelector('parsererror');
- 174 :
if (parsererror !== null) {
- 175 :
const errorMsg = parsererror.textContent;
- 176 :
const error = new Error(errorMsg);
- 177 :
if (includePosition) {
- 178 :
const lineNumber = parseInt(errorMsg.match(/line[\s\w]+?(\d+)/i)[1]);
- 179 :
const columnNumber = parseInt(errorMsg.match(/column[\s\w]+?(\d+)/i)[1]);
- 180 :
error.lineNumber = lineNumber;
- 181 :
error.columnNumber = columnNumber;
- 182 :
}
- 183 :
return error;
- 184 :
} else {
- 185 :
return null;
- 186 :
}
- 187 :
}
- 188 :
- 189 :
/**
- 190 :
* Returns true if the value is a String.
- 191 :
* @param {*} val
- 192 :
* @returns {Boolean}
- 193 :
* @static
- 194 :
*/
- 195 :
static isString(val) {
- 196 :
return typeof val === 'string';
- 197 :
}
- 198 :
- 199 :
/**
- 200 :
* Returns true if the value is a Number.
- 201 :
* @param {*} val
- 202 :
* @returns {Boolean}
- 203 :
* @static
- 204 :
*/
- 205 :
static isNumber(val) {
- 206 :
return typeof val === 'number';
- 207 :
}
- 208 :
- 209 :
/**
- 210 :
* Returns true if the value is a Boolean.
- 211 :
* @param {*} val
- 212 :
* @returns {Boolean}
- 213 :
* @static
- 214 :
*/
- 215 :
static isBoolean(val) {
- 216 :
return typeof val === 'boolean';
- 217 :
}
- 218 :
- 219 :
/**
- 220 :
* Returns true if the value is Undefined.
- 221 :
* @param {*} val
- 222 :
* @returns {Boolean}
- 223 :
* @static
- 224 :
*/
- 225 :
static isUndefined(val) {
- 226 :
return typeof val === 'undefined';
- 227 :
}
- 228 :
- 229 :
/**
- 230 :
* Returns true if the value is an Array.
- 231 :
* @param {*} val
- 232 :
* @returns {Boolean}
- 233 :
* @static
- 234 :
*/
- 235 :
static isArray(val) {
- 236 :
return Object.prototype.toString.call(val) === '[object Array]';
- 237 :
}
- 238 :
- 239 :
/**
- 240 :
* Returns true if the value is an Object.
- 241 :
* @param {*} val
- 242 :
* @returns {Boolean}
- 243 :
* @static
- 244 :
*/
- 245 :
static isObject(val) {
- 246 :
return Object.prototype.toString.call(val) === '[object Object]';
- 247 :
}
- 248 :
- 249 :
/**
- 250 :
* Returns true if the value is Null.
- 251 :
* @param {*} val
- 252 :
* @returns {Boolean}
- 253 :
* @static
- 254 :
*/
- 255 :
static isNull(val) {
- 256 :
return Object.prototype.toString.call(val) === '[object Null]';
- 257 :
}
- 258 :
- 259 :
/**
- 260 :
* Returns true if the value is a Node.
- 261 :
* @param {*} val
- 262 :
* @returns {Boolean}
- 263 :
* @static
- 264 :
*/
- 265 :
static isNode(val) {
- 266 :
return val instanceof Node;
- 267 :
}
- 268 :
- 269 :
/**
- 270 :
* Returns true if the value is a Function.
- 271 :
* @param {*} val
- 272 :
* @returns {Boolean}
- 273 :
* @static
- 274 :
*/
- 275 :
static isFunction(val) {
- 276 :
const typeString = Object.prototype.toString.call(val);
- 277 :
return typeString === '[object Function]' || typeString === '[object AsyncFunction]';
- 278 :
}
- 279 :
- 280 :
/**
- 281 :
* Returns true if the value is a Promise.
- 282 :
* @param {*} val
- 283 :
* @returns {Boolean}
- 284 :
* @static
- 285 :
*/
- 286 :
static isPromise(val) {
- 287 :
// ES6 promise detection
- 288 :
// return Object.prototype.toString.call(val) === '[object Promise]';
- 289 :
- 290 :
// general promise detection
- 291 :
return !!val && (typeof val === 'object' || typeof val === 'function') && typeof val.then === 'function';
- 292 :
}
- 293 :
- 294 :
/**
- 295 :
* Returns true if the value is a Blob.
- 296 :
* @param {*} val
- 297 :
* @returns {Boolean}
- 298 :
* @static
- 299 :
*/
- 300 :
static isBlob(val) {
- 301 :
return val instanceof Blob;
- 302 :
}
- 303 :
- 304 :
/**
- 305 :
* Takes a MIME type and returns the related file extension.
- 306 :
* Only handles file types supported by Voyant.
- 307 :
* @param {String} mimeType
- 308 :
* @returns {String}
- 309 :
* @static
- 310 :
*/
- 311 :
static getFileExtensionFromMimeType(mimeType) {
- 312 :
mimeType = mimeType.trim().toLowerCase();
- 313 :
switch (mimeType) {
- 314 :
case 'application/atom+xml':
- 315 :
return 'xml';
- 316 :
case 'application/rss+xml':
- 317 :
return 'xml';
- 318 :
case 'application/xml':
- 319 :
return 'xml';
- 320 :
case 'text/xml':
- 321 :
return 'xml';
- 322 :
case 'application/xhtml+xml':
- 323 :
return 'xhtml';
- 324 :
case 'text/html':
- 325 :
return 'html';
- 326 :
case 'text/plain':
- 327 :
return 'txt';
- 328 :
case 'application/pdf':
- 329 :
return 'pdf';
- 330 :
case 'application/json':
- 331 :
return 'json';
- 332 :
case 'application/vnd.apple.pages':
- 333 :
return 'pages';
- 334 :
case 'application/rtf':
- 335 :
return 'rtf';
- 336 :
case 'application/vnd.oasis.opendocument.text':
- 337 :
return 'odt';
- 338 :
case 'application/epub+zip':
- 339 :
return 'epub';
- 340 :
case 'application/msword':
- 341 :
return 'doc';
- 342 :
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
- 343 :
return 'docx';
- 344 :
case 'application/vnd.ms-excel':
- 345 :
return 'xls';
- 346 :
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
- 347 :
return 'xlsx';
- 348 :
case 'application/zip':
- 349 :
return 'zip';
- 350 :
case 'application/gzip':
- 351 :
return 'gzip';
- 352 :
case 'application/x-bzip2':
- 353 :
return 'bzip2';
- 354 :
default:
- 355 :
if (mimeType.indexOf('text') === 0) {
- 356 :
return 'txt'; // fallback
- 357 :
} else {
- 358 :
return undefined;
- 359 :
}
- 360 :
}
- 361 :
}
- 362 :
- 363 :
/**
- 364 :
* Takes a file extension and returns the corresponding Voyant Document Format name.
- 365 :
* @param {String} fileExtension
- 366 :
* @returns {String}
- 367 :
* @static
- 368 :
*/
- 369 :
static getVoyantDocumentFormatFromFileExtension(fileExtension) {
- 370 :
fileExtension = fileExtension.trim().toLowerCase();
- 371 :
switch(fileExtension) {
- 372 :
case 'txt':
- 373 :
return 'text';
- 374 :
case 'xhtml':
- 375 :
return 'html';
- 376 :
case 'doc':
- 377 :
return 'msword';
- 378 :
case 'docx':
- 379 :
return 'mswordx';
- 380 :
case 'xls':
- 381 :
return 'xlsx';
- 382 :
case 'zip':
- 383 :
return 'archive';
- 384 :
case 'gzip':
- 385 :
case 'bzip2':
- 386 :
return 'compressed';
- 387 :
default:
- 388 :
return fileExtension;
- 389 :
}
- 390 :
}
- 391 :
}
- 392 :
- 393 :
export default Util;