label-studio
Version:
Data Labeling Tool that is backend agnostic and can be embedded into your applications
313 lines (257 loc) • 6.02 kB
JavaScript
import React from "react";
import { getType, getParentOfType } from "mobx-state-tree";
import { parseString } from "xml2js";
import Registry from "./Registry";
import { guidGenerator } from "./Helpers";
/**
* Clone React Tree
* @param {*} items
* @param {*} attrs
*/
function cloneReactTree(items, attrs) {
let clone = null;
clone = function(children) {
const res = [];
React.Children.forEach(children, function(child) {
let el;
if (child.props) {
let moreProps = {};
if (typeof attrs === "function") {
moreProps = attrs(child);
} else if (typeof attrs === "object") {
moreProps = attrs;
}
el = React.cloneElement(child, moreProps, clone(child.props.children));
} else {
el = child;
}
res.push(el);
});
return res;
};
return clone(items);
}
/**
* Function to convert CSS string to object
* @param {string} style
* @returns {object}
*/
function cssConverter(style) {
if (!style) return null;
let result = {},
attributes = style.split(";"),
firstIndexOfColon,
i,
key,
value;
for (i = 0; i < attributes.length; i++) {
firstIndexOfColon = attributes[i].indexOf(":");
key = attributes[i].substring(0, firstIndexOfColon);
value = attributes[i].substring(firstIndexOfColon + 1);
key = key.replace(/ /g, "");
if (key.length < 1) {
continue;
}
if (value[0] === " ") {
value = value.substring(1);
}
if (value[value.length - 1] === " ") {
value = value.substring(0, value.length - 1);
}
var ukey = key.replace(/(-.)/g, x => x[1].toUpperCase());
result[ukey] = value;
}
return result;
}
/**
*
* @param {*} attrs
*/
function attrsToProps(attrs) {
const props = {};
if (!attrs) return props;
for (let item of Object.keys(attrs)) {
/**
* Convert node of Tree to boolean value
*/
if (item !== "value" && (attrs[item] === "true" || attrs[item] === "false")) {
props[item.toLowerCase()] = JSON.parse(attrs[item]);
} else {
props[item.toLowerCase()] = attrs[item];
}
}
return props;
}
/**
*
* @param {*} html
*/
function treeToModel(html) {
/**
* Remove all line breaks from a string
* @param {string}
* @returns {string}
*/
function removeAllBreaks(data) {
return data.replace(/(\r\n|\n|\r)/gm, "");
}
/**
* Edit all self closing tags from XML View
* TODO: Fix bug: if the value of <Choice /> or another tag contains "/>" function return error
* @param {string} data
* @returns {string}
*/
function editSelfClosingTags(data) {
let split = data.split("/>");
let newData = "";
for (let i = 0; i < split.length - 1; i++) {
let edsplit = split[i].split("<");
newData += split[i] + "></" + edsplit[edsplit.length - 1].split(" ")[0] + ">";
}
return newData + split[split.length - 1];
}
/**
* Generate new node
* @param {object} node
*/
function addNode(node) {
if (!node.$$) return null;
let text = null;
const res = [];
for (let chld of node.$$) {
if (chld["#name"] !== "__text__") {
const data = buildData(chld);
const children = addNode(chld);
if (children) {
if (typeof children === "string") data["value"] = children;
else data.children = children;
}
res.push(data);
} else {
text = chld._;
}
}
return res.length === 0 ? text : res;
}
/**
* Generate obj with main data
*/
function buildData(node) {
const data = attrsToProps(node.$);
/**
* Generation id of node
*/
data["id"] = guidGenerator();
/**
* Build type name
*/
data["type"] = node["#name"].toLowerCase();
return data;
}
const htmlWithotBreaks = removeAllBreaks(html);
const htmlSelfClosingTags = editSelfClosingTags(htmlWithotBreaks);
let document;
// it's actually a sync function, but there is no sync interface
// because of some backwards compat
parseString(
htmlSelfClosingTags,
{
explicitChildren: true,
preserveChildrenOrder: true,
charsAsChildren: true,
},
function(err, result) {
document = result;
},
);
const root = buildData(Object.values(document)[0]);
root.children = addNode(Object.values(document)[0]);
return root;
}
/**
* Render items of tree
* @param {*} el
*/
function renderItem(el) {
const View = Registry.getViewByModel(getType(el).name);
if (!View) {
throw new Error("No view for model:" + getType(el).name);
}
return <View key={guidGenerator()} item={el} />;
}
/**
*
* @param {*} item
*/
function renderChildren(item) {
if (item && item.children && item.children.length) {
return item.children.map(el => {
return renderItem(el);
});
} else {
return null;
}
}
/**
*
* @param {*} name
* @param {*} tree
*/
function findInterface(name, tree) {
let fn;
fn = function(node) {
if (getType(node).name === name) return node;
if (node.children) {
for (let chld of node.children) {
const res = fn(chld);
if (res) return res;
}
}
};
return fn(tree);
}
/**
*
* @param {*} obj
* @param {*} classes
*/
function findParentOfType(obj, classes) {
for (let c of classes) {
try {
const p = getParentOfType(obj, c);
if (p) return p;
} catch (err) {}
}
return null;
}
/**
*
* @param {*} obj
* @param {*} classes
*/
function filterChildrenOfType(obj, classes) {
const res = [];
let fn;
fn = function(node) {
for (let c of classes) {
if (getType(node).name === c) res.push(node);
}
if (node.children) {
for (let chld of node.children) {
fn(chld);
}
}
};
fn(obj);
return res;
}
export default {
cloneReactTree,
renderItem,
renderChildren,
treeToModel,
findInterface,
findParentOfType,
filterChildrenOfType,
cssConverter,
};