diff --git a/ui/builder.html b/ui/builder.html index 5faebfd..6e6835f 100644 --- a/ui/builder.html +++ b/ui/builder.html @@ -286,7 +286,7 @@ input,textarea,button,select{font-family:inherit} ποΈ CTStruct - v0.8 + v0.9 @@ -1328,6 +1328,45 @@ function getCollectKeys(root){ return keys; } +// ββ μ€ν€λ§ λ§΅: μμ§ μΉμ μ νμ μΉμ β λ μ½λ νλ μ μ ββ +function buildSchemaMap(root){ + const map={}; + function walk(n){ + for(const c of n.children??[]){ + if(c.type!=='SECTION') continue; + const key=(c.attributes?.mark??'')+(c.content??''); + const childSecs=(c.children??[]).filter(ch=>ch.type==='SECTION'&&ch.attributes?.mark); + if(childSecs.length) map[key]=childSecs.map(ch=>({mark:ch.attributes.mark,content:ch.content??''})); + walk(c); + } + } + walk(root); + return map; +} + +// ββ λ μ½λ ν μ€ νμ±: inline λ§ν¬ κΈ°μ€ λΆλ¦¬ ββββββββββββββ +function parseRecordLine(line, schema){ + const sorted=schema.map(s=>s.mark).filter(Boolean).sort((a,b)=>b.length-a.length); + if(!sorted.length) return null; + const hits=[]; + let i=0; + while(i${isCol?'π₯μμ§':'πκ³ μ '}`:''; - let html=`${depth?'βΈ':''}${esc(m)}${esc(n)}${roleHtml}${pl?`${esc(pl)}κ°/μ€`:''}`; - for(const c of sec.children??[]) if(c.type==='SECTION') html+=walkSec(c,depth+1); + const schemaSecs=(sec.children??[]).filter(c=>c.type==='SECTION'&&c.attributes?.mark); + const hasSchema=isCol&&schemaSecs.length>0; + const schemaHtml=hasSchema + ?schemaSecs.map(f=>`${esc(f.mark)}${esc(f.content)}`).join('') + :''; + let html=`${depth?'βΈ':''}${esc(m)}${esc(n)}${roleHtml}${pl?`${esc(pl)}κ°/μ€`:''}${schemaHtml}`; + // μ€ν€λ§ μμμ μΉ©μ μΈλΌμΈμΌλ‘ νμνμΌλ―λ‘ μ¬κ· μ μΈ + if(!hasSchema) for(const c of sec.children??[]) if(c.type==='SECTION') html+=walkSec(c,depth+1); return html; } const tm=FT.attributes?.mark??''; @@ -1372,10 +1417,10 @@ function splitIntoInstances(rawText, titleMark){ return instances; } -function parseInstance(text, titleMark, sectionMarks){ +function parseInstance(text, titleMark, sectionMarks, schemaMap){ const sorted=sectionMarks.slice().sort((a,b)=>b.length-a.length); const result={sections:{}}; - let curKey=null; + let curKey=null, curSchema=null; for(const rawLine of text.split('\n')){ const line=rawLine.trim(); if(!line) continue; if(titleMark&&line.startsWith(titleMark)) continue; @@ -1384,11 +1429,18 @@ function parseInstance(text, titleMark, sectionMarks){ if(line.startsWith(mark)){ curKey=mark+line.slice(mark.length).trim(); if(!result.sections[curKey]) result.sections[curKey]=[]; + curSchema=schemaMap?.[curKey]??null; matched=true; break; } } if(!matched&&curKey){ - result.sections[curKey].push(...line.split(/\s+/).filter(Boolean)); + if(curSchema?.length){ + const rec=parseRecordLine(line,curSchema); + if(rec) result.sections[curKey].push(rec); + else result.sections[curKey].push(...line.split(/\s+/).filter(Boolean)); + } else { + result.sections[curKey].push(...line.split(/\s+/).filter(Boolean)); + } } } return result; @@ -1408,12 +1460,13 @@ function parseAndMerge(){ fillerMerged={}; const collectKeys=getCollectKeys(FT); const selective=collectKeys.size>0; + const schemaMap=buildSchemaMap(FT); for(const text of instances){ - const parsed=parseInstance(text,titleMark,sectionMarks); - for(const [key,tokens] of Object.entries(parsed.sections)){ + const parsed=parseInstance(text,titleMark,sectionMarks,schemaMap); + for(const [key,entries] of Object.entries(parsed.sections)){ if(selective&&!collectKeys.has(key)) continue; if(!fillerMerged[key]) fillerMerged[key]=[]; - fillerMerged[key].push(...tokens); + fillerMerged[key].push(...entries); } } instEl.textContent=instances.length?`(${instances.length}κ° μμ)`:''; @@ -1431,14 +1484,27 @@ function mergedToText(){ const key=mark+content; const isCol=selective?(sec.attributes?.collect==='true'):true; const perLine=parseInt(sec.attributes?.per_line??'0')||0; + const schemaSecs=(sec.children??[]).filter(c=>c.type==='SECTION'&&c.attributes?.mark); + const hasSchema=isCol&&schemaSecs.length>0; let t=mark+content+'\n'; if(isCol){ - const tokens=fillerMerged[key]??[]; - if(tokens.length){ - if(perLine>0){ - for(let i=0;ientry[f.mark]!=null?f.mark+entry[f.mark]:'').filter(Boolean).join(''); + if(line) t+=line+'\n'; + } + } else { + // λ¨μ λͺ¨λ: ν ν° per_line μΆλ ₯ + const tokens=entries.filter(e=>typeof e==='string'); + if(tokens.length){ + if(perLine>0){ + for(let i=0;ic.type==='SECTION'&&c.attributes?.mark); + const hasSchema=isCol&&schemaSecs.length>0; + let newChildren; if(isCol){ - const tokens=fillerMerged[key]??[]; - valChildren=tokens.map((t,i)=>({id:'mv'+i,type:'VALUE',data_type:'string',value:t})); + const entries=fillerMerged[key]??[]; + if(hasSchema){ + // λ μ½λ λͺ¨λ: 첫 νλ = λΆλͺ¨ SECTION, λλ¨Έμ§ = μμ SECTION + newChildren=entries.flatMap((entry,i)=>{ + if(typeof entry==='string') return [{id:'mv'+i,type:'VALUE',data_type:'string',value:entry}]; + const fields=schemaSecs.filter(f=>entry[f.mark]!=null); + if(!fields.length) return []; + const[first,...rest]=fields; + return [{ + id:'rec'+i, type:'SECTION', + content:entry[first.mark]??'', + attributes:{mark:first.mark}, + children:rest.map((f,j)=>({ + id:'rf'+i+'_'+j, type:'SECTION', + content:entry[f.mark]??'', + attributes:{mark:f.mark}, children:[] + })) + }]; + }); + } else { + newChildren=entries.filter(e=>typeof e==='string') + .map((t,i)=>({id:'mv'+i,type:'VALUE',data_type:'string',value:t})); + } } else { - valChildren=(sec.children??[]).filter(c=>c.type==='VALUE'); + newChildren=(sec.children??[]).filter(c=>c.type==='VALUE'); } - const secChildren=(sec.children??[]).filter(c=>c.type==='SECTION').map(buildSec); - return {...sec, children:[...valChildren,...secChildren]}; + const secChildren=hasSchema?[]:(sec.children??[]).filter(c=>c.type==='SECTION').map(buildSec); + return{...sec, children:[...newChildren,...secChildren]}; } const merged={...FT, children:(FT.children??[]).map(c=>c.type==='SECTION'?buildSec(c):c)}; return JSON.stringify(merged,null,2);