refactor: remove XML export, JSON-only for .rfflow files
- Remove XML builder, format query param, and XML tests - Simplify ExportFlowModal (no format picker) - Simplify rfflowParser (JSON-only) - Remove format field from schemas and types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { RFFlowFile, FlowExportData, FlowExportCategory } from '@/types'
|
||||
import type { RFFlowFile, FlowExportData } from '@/types'
|
||||
|
||||
export class RFFlowParseError extends Error {
|
||||
constructor(message: string) {
|
||||
@@ -10,20 +10,12 @@ export class RFFlowParseError extends Error {
|
||||
export function parseRFFlowFile(content: string): RFFlowFile {
|
||||
const trimmed = content.trim()
|
||||
|
||||
if (trimmed.startsWith('{')) {
|
||||
return parseJSON(trimmed)
|
||||
if (!trimmed.startsWith('{')) {
|
||||
throw new RFFlowParseError('Invalid file format. Expected JSON.')
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('<?xml') || trimmed.startsWith('<rfflow')) {
|
||||
return parseXML(trimmed)
|
||||
}
|
||||
|
||||
throw new RFFlowParseError('Unrecognized file format. Expected JSON or XML.')
|
||||
}
|
||||
|
||||
function parseJSON(content: string): RFFlowFile {
|
||||
try {
|
||||
const data = JSON.parse(content)
|
||||
const data = JSON.parse(trimmed)
|
||||
validateEnvelope(data)
|
||||
return data as RFFlowFile
|
||||
} catch (err) {
|
||||
@@ -32,99 +24,6 @@ function parseJSON(content: string): RFFlowFile {
|
||||
}
|
||||
}
|
||||
|
||||
function parseXML(content: string): RFFlowFile {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(content, 'application/xml')
|
||||
|
||||
const parseError = doc.querySelector('parsererror')
|
||||
if (parseError) {
|
||||
throw new RFFlowParseError('Invalid XML file')
|
||||
}
|
||||
|
||||
const root = doc.documentElement
|
||||
if (root.tagName !== 'rfflow') {
|
||||
throw new RFFlowParseError('Root element must be <rfflow>')
|
||||
}
|
||||
|
||||
const getText = (parent: Element, tag: string): string => {
|
||||
const el = parent.querySelector(`:scope > ${tag}`)
|
||||
return el?.textContent?.trim() ?? ''
|
||||
}
|
||||
|
||||
const rfflowVersion = root.getAttribute('version') || getText(root, 'rfflow_version') || '1.0'
|
||||
const exportedAt = getText(root, 'exported_at')
|
||||
const sourceApp = getText(root, 'source_app') || 'ResolutionFlow'
|
||||
|
||||
const flowEl = root.querySelector(':scope > flow')
|
||||
if (!flowEl) {
|
||||
throw new RFFlowParseError('Missing <flow> element')
|
||||
}
|
||||
|
||||
// Parse category
|
||||
let category: FlowExportCategory | null = null
|
||||
const catEl = flowEl.querySelector(':scope > category')
|
||||
if (catEl) {
|
||||
const catName = getText(catEl, 'name')
|
||||
const catSlug = getText(catEl, 'slug')
|
||||
if (catName && catSlug) {
|
||||
category = { name: catName, slug: catSlug }
|
||||
}
|
||||
}
|
||||
|
||||
// Parse tags
|
||||
const tags: string[] = []
|
||||
const tagsEl = flowEl.querySelector(':scope > tags')
|
||||
if (tagsEl) {
|
||||
tagsEl.querySelectorAll(':scope > tag').forEach((tagEl) => {
|
||||
const val = tagEl.textContent?.trim()
|
||||
if (val) tags.push(val)
|
||||
})
|
||||
}
|
||||
|
||||
// Parse tree_structure (stored as JSON string)
|
||||
const tsText = getText(flowEl, 'tree_structure')
|
||||
let treeStructure: Record<string, unknown>
|
||||
try {
|
||||
treeStructure = JSON.parse(tsText)
|
||||
} catch {
|
||||
throw new RFFlowParseError('Invalid tree_structure JSON in XML')
|
||||
}
|
||||
|
||||
// Parse intake_form (optional, stored as JSON string)
|
||||
let intakeForm: Record<string, unknown>[] | null = null
|
||||
const ifText = getText(flowEl, 'intake_form')
|
||||
if (ifText) {
|
||||
try {
|
||||
intakeForm = JSON.parse(ifText)
|
||||
} catch {
|
||||
throw new RFFlowParseError('Invalid intake_form JSON in XML')
|
||||
}
|
||||
}
|
||||
|
||||
const flow: FlowExportData = {
|
||||
name: getText(flowEl, 'name'),
|
||||
description: getText(flowEl, 'description') || null,
|
||||
tree_type: getText(flowEl, 'tree_type') as FlowExportData['tree_type'],
|
||||
version: parseInt(getText(flowEl, 'version') || '1', 10),
|
||||
author_name: getText(flowEl, 'author_name') || null,
|
||||
category,
|
||||
tags,
|
||||
tree_structure: treeStructure,
|
||||
intake_form: intakeForm,
|
||||
}
|
||||
|
||||
const result: RFFlowFile = {
|
||||
rfflow_version: rfflowVersion,
|
||||
exported_at: exportedAt,
|
||||
source_app: sourceApp,
|
||||
format: 'xml',
|
||||
flow,
|
||||
}
|
||||
|
||||
validateEnvelope(result)
|
||||
return result
|
||||
}
|
||||
|
||||
function validateEnvelope(data: unknown): asserts data is RFFlowFile {
|
||||
const obj = data as Record<string, unknown>
|
||||
|
||||
@@ -146,8 +45,8 @@ function validateEnvelope(data: unknown): asserts data is RFFlowFile {
|
||||
throw new RFFlowParseError('Flow must have a tree_structure')
|
||||
}
|
||||
|
||||
const validTypes = ['troubleshooting', 'procedural', 'maintenance']
|
||||
if (!validTypes.includes(flow.tree_type as string)) {
|
||||
const validTypes: FlowExportData['tree_type'][] = ['troubleshooting', 'procedural', 'maintenance']
|
||||
if (!validTypes.includes(flow.tree_type as FlowExportData['tree_type'])) {
|
||||
throw new RFFlowParseError(`Invalid tree_type: ${flow.tree_type}`)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user