You've already forked DataMate
feat(annotation): 支持从XML配置解析标注任务模板
- 添加 XML 配置解析功能,支持从 Label Studio XML 格式提取 objects 和 labels - 优化模板配置加载逻辑,优先使用 configuration 字段,否则从 labelConfig 解析 - 增加对多种数据对象类型的解析支持(Image、Text、Audio 等) - 实现标签控件类型的完整解析(Choices、Labels、RectangleLabels 等)
This commit is contained in:
@@ -121,18 +121,29 @@ export default function CreateAnnotationTask({
|
|||||||
});
|
});
|
||||||
setSelectedDatasetId(taskDetail.datasetId);
|
setSelectedDatasetId(taskDetail.datasetId);
|
||||||
|
|
||||||
// 填充模板配置
|
// 配置可能在 template.configuration 或 taskDetail.configuration
|
||||||
if (taskDetail.configuration) {
|
const configuration = taskDetail.template?.configuration || taskDetail.configuration;
|
||||||
const { objects, labels } = taskDetail.configuration;
|
const labelConfig = taskDetail.template?.labelConfig || taskDetail.labelConfig;
|
||||||
|
|
||||||
|
// 设置 XML 配置用于预览
|
||||||
|
if (labelConfig) {
|
||||||
|
setCustomXml(labelConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充模板配置:优先使用 configuration,否则从 labelConfig 解析
|
||||||
|
if (configuration && configuration.objects?.length > 0) {
|
||||||
|
const { objects, labels } = configuration;
|
||||||
manualForm.setFieldsValue({
|
manualForm.setFieldsValue({
|
||||||
objects: objects || [],
|
objects: objects || [],
|
||||||
labels: labels || [],
|
labels: labels || [],
|
||||||
});
|
});
|
||||||
}
|
} else if (labelConfig) {
|
||||||
|
// 从 XML 解析配置
|
||||||
// 设置 XML 配置用于预览
|
const parsed = parseXmlToConfig(labelConfig);
|
||||||
if (taskDetail.labelConfig) {
|
manualForm.setFieldsValue({
|
||||||
setCustomXml(taskDetail.labelConfig);
|
objects: parsed.objects,
|
||||||
|
labels: parsed.labels,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑模式始终使用 custom 配置模式(不改变结构,只改标签)
|
// 编辑模式始终使用 custom 配置模式(不改变结构,只改标签)
|
||||||
@@ -243,6 +254,80 @@ export default function CreateAnnotationTask({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 从 Label Studio XML 配置解析出 objects 和 labels
|
||||||
|
const parseXmlToConfig = (xml: string): { objects: any[], labels: any[] } => {
|
||||||
|
const objects: any[] = [];
|
||||||
|
const labels: any[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(xml, "text/xml");
|
||||||
|
|
||||||
|
// 数据对象类型列表
|
||||||
|
const objectTypes = ["Image", "Text", "Audio", "Video", "HyperText", "Header", "Paragraphs", "TimeSeries", "TimeSeriesChannel"];
|
||||||
|
// 标签控件类型列表
|
||||||
|
const controlTypes = ["Choices", "Labels", "RectangleLabels", "PolygonLabels", "EllipseLabels", "KeyPointLabels", "BrushLabels", "TextArea", "Number", "DateTime", "Rating", "Taxonomy"];
|
||||||
|
|
||||||
|
// 解析数据对象
|
||||||
|
objectTypes.forEach(type => {
|
||||||
|
const elements = doc.getElementsByTagName(type);
|
||||||
|
for (let i = 0; i < elements.length; i++) {
|
||||||
|
const el = elements[i];
|
||||||
|
const name = el.getAttribute("name") || "";
|
||||||
|
const value = el.getAttribute("value") || "";
|
||||||
|
if (name) {
|
||||||
|
objects.push({ name, type, value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解析标签控件
|
||||||
|
controlTypes.forEach(type => {
|
||||||
|
const elements = doc.getElementsByTagName(type);
|
||||||
|
for (let i = 0; i < elements.length; i++) {
|
||||||
|
const el = elements[i];
|
||||||
|
const fromName = el.getAttribute("name") || "";
|
||||||
|
const toName = el.getAttribute("toName") || "";
|
||||||
|
const required = el.getAttribute("required") === "true";
|
||||||
|
|
||||||
|
if (fromName) {
|
||||||
|
const label: any = {
|
||||||
|
fromName,
|
||||||
|
toName,
|
||||||
|
type,
|
||||||
|
required,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析选项/标签值
|
||||||
|
if (type === "Choices") {
|
||||||
|
const choices: string[] = [];
|
||||||
|
const choiceElements = el.getElementsByTagName("Choice");
|
||||||
|
for (let j = 0; j < choiceElements.length; j++) {
|
||||||
|
const value = choiceElements[j].getAttribute("value");
|
||||||
|
if (value) choices.push(value);
|
||||||
|
}
|
||||||
|
label.options = choices;
|
||||||
|
} else if (["Labels", "RectangleLabels", "PolygonLabels", "EllipseLabels", "KeyPointLabels", "BrushLabels"].includes(type)) {
|
||||||
|
const labelValues: string[] = [];
|
||||||
|
const labelElements = el.getElementsByTagName("Label");
|
||||||
|
for (let j = 0; j < labelElements.length; j++) {
|
||||||
|
const value = labelElements[j].getAttribute("value");
|
||||||
|
if (value) labelValues.push(value);
|
||||||
|
}
|
||||||
|
label.labels = labelValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
labels.push(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse XML config:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { objects, labels };
|
||||||
|
};
|
||||||
|
|
||||||
const generateXmlFromConfig = (objects: any[], labels: any[]) => {
|
const generateXmlFromConfig = (objects: any[], labels: any[]) => {
|
||||||
let xml = '<View>\n';
|
let xml = '<View>\n';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user