Reference Source

src/viewer/scene/utils.js

/**
 * @private
 */
import {core} from "./core.js";

function xmlToJson(node, attributeRenamer) {
    if (node.nodeType === node.TEXT_NODE) {
        var v = node.nodeValue;
        if (v.match(/^\s+$/) === null) {
            return v;
        }
    } else if (node.nodeType === node.ELEMENT_NODE ||
        node.nodeType === node.DOCUMENT_NODE) {
        var json = {type: node.nodeName, children: []};

        if (node.nodeType === node.ELEMENT_NODE) {
            for (var j = 0; j < node.attributes.length; j++) {
                var attribute = node.attributes[j];
                var nm = attributeRenamer[attribute.nodeName] || attribute.nodeName;
                json[nm] = attribute.nodeValue;
            }
        }

        for (var i = 0; i < node.childNodes.length; i++) {
            var item = node.childNodes[i];
            var j = xmlToJson(item, attributeRenamer);
            if (j) json.children.push(j);
        }

        return json;
    }
}

/**
 * @private
 */
function clone(ob) {
    return JSON.parse(JSON.stringify(ob));
}

/**
 * @private
 */
var guidChars = [["0", 10], ["A", 26], ["a", 26], ["_", 1], ["$", 1]].map(function (a) {
    var li = [];
    var st = a[0].charCodeAt(0);
    var en = st + a[1];
    for (var i = st; i < en; ++i) {
        li.push(i);
    }
    return String.fromCharCode.apply(null, li);
}).join("");

/**
 * @private
 */
function b64(v, len) {
    var r = (!len || len === 4) ? [0, 6, 12, 18] : [0, 6];
    return r.map(function (i) {
        return guidChars.substr(parseInt(v / (1 << i)) % 64, 1)
    }).reverse().join("");
}

/**
 * @private
 */
function compressGuid(g) {
    var bs = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30].map(function (i) {
        return parseInt(g.substr(i, 2), 16);
    });
    return b64(bs[0], 2) + [1, 4, 7, 10, 13].map(function (i) {
        return b64((bs[i] << 16) + (bs[i + 1] << 8) + bs[i + 2]);
    }).join("");
}

/**
 * @private
 */
function findNodeOfType(m, t) {
    var li = [];
    var _ = function (n) {
        if (n.type === t) li.push(n);
        (n.children || []).forEach(function (c) {
            _(c);
        });
    };
    _(m);
    return li;
}

/**
 * @private
 */
function timeout(dt) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, dt);
    });
}

/**
 * @private
 */
function httpRequest(args) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open(args.method || "GET", args.url, true);
        xhr.onload = function (e) {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(xhr.responseXML);
                } else {
                    reject(xhr.statusText);
                }
            }
        };
        xhr.send(null);
    });
}

/**
 * @private
 */
const queryString = function () {
    // This function is anonymous, is executed immediately and
    // the return value is assigned to QueryString!
    var query_string = {};
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        // If first entry with this name
        if (typeof query_string[pair[0]] === "undefined") {
            query_string[pair[0]] = decodeURIComponent(pair[1]);
            // If second entry with this name
        } else if (typeof query_string[pair[0]] === "string") {
            var arr = [query_string[pair[0]], decodeURIComponent(pair[1])];
            query_string[pair[0]] = arr;
            // If third or later entry with this name
        } else {
            query_string[pair[0]].push(decodeURIComponent(pair[1]));
        }
    }
    return query_string;
}();

/**
 * @private
 */
function loadJSON(url, ok, err) {
    // Avoid checking ok and err on each use.
    var defaultCallback = (_value) => undefined;
    ok = ok || defaultCallback;
    err = err || defaultCallback;

    var request = new XMLHttpRequest();
    request.overrideMimeType("application/json");
    request.open('GET', url, true);
    request.addEventListener('load', function (event) {
        var response = event.target.response;
        if (this.status === 200) {
            var json;
            try {
                json = JSON.parse(response);
            } catch (e) {
                err(`utils.loadJSON(): Failed to parse JSON response - ${e}`);
            }
            ok(json);
        } else if (this.status === 0) {
            // Some browsers return HTTP Status 0 when using non-http protocol
            // e.g. 'file://' or 'data://'. Handle as success.
            console.warn('loadFile: HTTP Status 0 received.');
            try {
                ok(JSON.parse(response));
            } catch (e) {
                err(`utils.loadJSON(): Failed to parse JSON response - ${e}`);
            }
        } else {
            err(event);
        }
    }, false);

    request.addEventListener('error', function (event) {
        err(event);
    }, false);
    request.send(null);
}

/**
 * @private
 */
function loadArraybuffer(url, ok, err) {
    // Check for data: URI
    var defaultCallback = (_value) => undefined;
    ok = ok || defaultCallback;
    err = err || defaultCallback;
    const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
    const dataUriRegexResult = url.match(dataUriRegex);
    if (dataUriRegexResult) { // Safari can't handle data URIs through XMLHttpRequest
        const isBase64 = !!dataUriRegexResult[2];
        var data = dataUriRegexResult[3];
        data = window.decodeURIComponent(data);
        if (isBase64) {
            data = window.atob(data);
        }
        try {
            const buffer = new ArrayBuffer(data.length);
            const view = new Uint8Array(buffer);
            for (var i = 0; i < data.length; i++) {
                view[i] = data.charCodeAt(i);
            }
            core.scheduleTask(() => {
                ok(buffer);
            });
        } catch (error) {
            core.scheduleTask(() => {
                err(error);
            });
        }
    } else {
        const request = new XMLHttpRequest();
        request.open('GET', url, true);
        request.responseType = 'arraybuffer';
        request.onreadystatechange = function () {
            if (request.readyState === 4) {
                if (request.status === 200) {
                    ok(request.response);
                } else {
                    err('loadArrayBuffer error : ' + request.response);
                }
            }
        };
        request.send(null);
    }
}

/**
 Tests if the given object is an array
 @private
 */
function isArray(value) {
    return value && !(value.propertyIsEnumerable('length')) && typeof value === 'object' && typeof value.length === 'number';
}

/**
 Tests if the given value is a string
 @param value
 @returns {Boolean}
 @private
 */
function isString(value) {
    return (typeof value === 'string' || value instanceof String);
}

/**
 Tests if the given value is a number
 @param value
 @returns {Boolean}
 @private
 */
function isNumeric(value) {
    return !isNaN(parseFloat(value)) && isFinite(value);
}

/**
 Tests if the given value is an ID
 @param value
 @returns {Boolean}
 @private
 */
function isID(value) {
    return utils.isString(value) || utils.isNumeric(value);
}

/**
 Tests if the given components are the same, where the components can be either IDs or instances.
 @param c1
 @param c2
 @returns {Boolean}
 @private
 */
function isSameComponent(c1, c2) {
    if (!c1 || !c2) {
        return false;
    }
    const id1 = (utils.isNumeric(c1) || utils.isString(c1)) ? `${c1}` : c1.id;
    const id2 = (utils.isNumeric(c2) || utils.isString(c2)) ? `${c2}` : c2.id;
    return id1 === id2;
}

/**
 Tests if the given value is a function
 @param value
 @returns {Boolean}
 @private
 */
function isFunction(value) {
    return (typeof value === "function");
}

/**
 Tests if the given value is a JavaScript JSON object, eg, ````{ foo: "bar" }````.
 @param value
 @returns {Boolean}
 @private
 */
function isObject(value) {
    const objectConstructor = {}.constructor;
    return (!!value && value.constructor === objectConstructor);
}

/** Returns a shallow copy
 */
function copy(o) {
    return utils.apply(o, {});
}

/** Add properties of o to o2, overwriting them on o2 if already there
 */
function apply(o, o2) {
    for (const name in o) {
        if (o.hasOwnProperty(name)) {
            o2[name] = o[name];
        }
    }
    return o2;
}

/**
 Add non-null/defined properties of o to o2
 @private
 */
function apply2(o, o2) {
    for (const name in o) {
        if (o.hasOwnProperty(name)) {
            if (o[name] !== undefined && o[name] !== null) {
                o2[name] = o[name];
            }
        }
    }
    return o2;
}

/**
 Add properties of o to o2 where undefined or null on o2
 @private
 */
function applyIf(o, o2) {
    for (const name in o) {
        if (o.hasOwnProperty(name)) {
            if (o2[name] === undefined || o2[name] === null) {
                o2[name] = o[name];
            }
        }
    }
    return o2;
}

/**
 Returns true if the given map is empty.
 @param obj
 @returns {Boolean}
 @private
 */
function isEmptyObject(obj) {
    for (const name in obj) {
        if (obj.hasOwnProperty(name)) {
            return false;
        }
    }
    return true;
}

/**
 Returns the given ID as a string, in quotes if the ID was a string to begin with.

 This is useful for logging IDs.

 @param {Number| String} id The ID
 @returns {String}
 @private
 */
function inQuotes(id) {
    return utils.isNumeric(id) ? (`${id}`) : (`'${id}'`);
}

/**
 Returns the concatenation of two typed arrays.
 @param a
 @param b
 @returns {*|a}
 @private
 */
function concat(a, b) {
    const c = new a.constructor(a.length + b.length);
    c.set(a);
    c.set(b, a.length);
    return c;
}

function flattenParentChildHierarchy(root) {
    var list = [];

    function visit(node) {
        node.id = node.uuid;
        delete node.oid;
        list.push(node);
        var children = node.children;

        if (children) {
            for (var i = 0, len = children.length; i < len; i++) {
                const child = children[i];
                child.parent = node.id;
                visit(children[i]);
            }
        }
        node.children = [];
    }

    visit(root);
    return list;
}

/**
 * @private
 */
const utils = {
    xmlToJson: xmlToJson,
    clone: clone,
    compressGuid: compressGuid,
    findNodeOfType: findNodeOfType,
    timeout: timeout,
    httpRequest: httpRequest,
    loadJSON: loadJSON,
    loadArraybuffer: loadArraybuffer,
    queryString: queryString,
    isArray: isArray,
    isString: isString,
    isNumeric: isNumeric,
    isID: isID,
    isSameComponent: isSameComponent,
    isFunction: isFunction,
    isObject: isObject,
    copy: copy,
    apply: apply,
    apply2: apply2,
    applyIf: applyIf,
    isEmptyObject: isEmptyObject,
    inQuotes: inQuotes,
    concat: concat,
    flattenParentChildHierarchy: flattenParentChildHierarchy
};

export {utils};