From ecf6409cf8077943d7dee6b58d5162fbd024cf0c Mon Sep 17 00:00:00 2001 From: wkwong-ribose Date: Mon, 18 Oct 2021 16:54:43 +0800 Subject: [PATCH] Modified the app to support specific items in BS 6004 Example, commentary and some bug fix --- .../serialize/interface/supportinterface.ts | 7 +- src/smart/serialize/util/serailizeformater.ts | 4 +- .../ui/edit/components/DataTypeSelector.tsx | 1 + .../ui/edit/components/ReferenceSelector.tsx | 1 + .../ui/edit/components/RegistrySelector.tsx | 1 + src/smart/ui/edit/egateedit.tsx | 5 +- src/smart/ui/edit/processedit.tsx | 1 + src/smart/ui/editFunctions/EditWrapper.tsx | 8 + src/smart/ui/maineditor.tsx | 3 + src/smart/ui/menu/EditorReferenceMenu.tsx | 17 +- src/smart/ui/popover/AskIDForSaveMenu.tsx | 7 +- src/smart/utils/IOFunctions.ts | 15 +- src/smart/utils/ModelAddComponentHandler.ts | 8 +- src/smart/utils/ModelFunctions.ts | 9 + src/smart/utils/xml/BSIXML.ts | 309 ++++++++++++++++++ 15 files changed, 387 insertions(+), 9 deletions(-) create mode 100644 src/smart/utils/xml/BSIXML.ts diff --git a/src/smart/serialize/interface/supportinterface.ts b/src/smart/serialize/interface/supportinterface.ts index c0e79d08..c8c031bc 100644 --- a/src/smart/serialize/interface/supportinterface.ts +++ b/src/smart/serialize/interface/supportinterface.ts @@ -8,7 +8,12 @@ export enum VarType { BOOLEAN = 'TRUE/FALSE', } -export const NOTE_TYPES = ['NOTE', 'EXAMPLE', 'DEFINITION'] as const; +export const NOTE_TYPES = [ + 'NOTE', + 'EXAMPLE', + 'DEFINITION', + 'COMMENTARY', +] as const; export type NOTE_TYPE = typeof NOTE_TYPES[number]; export interface MMELMetadata extends MMELObject { diff --git a/src/smart/serialize/util/serailizeformater.ts b/src/smart/serialize/util/serailizeformater.ts index 54090d32..0aff9f58 100644 --- a/src/smart/serialize/util/serailizeformater.ts +++ b/src/smart/serialize/util/serailizeformater.ts @@ -344,7 +344,9 @@ function toApprovalModel(app: MMELApproval): string { if (app.actor !== '') { out += ' actor ' + app.actor + '\n'; } - out += ' modality ' + app.modality + '\n'; + if (app.modality !== '') { + out += ' modality ' + app.modality + '\n'; + } if (app.approver !== '') { out += ' approve_by ' + app.approver + '\n'; } diff --git a/src/smart/ui/edit/components/DataTypeSelector.tsx b/src/smart/ui/edit/components/DataTypeSelector.tsx index 091c9c96..91d95c3b 100644 --- a/src/smart/ui/edit/components/DataTypeSelector.tsx +++ b/src/smart/ui/edit/components/DataTypeSelector.tsx @@ -54,6 +54,7 @@ const DataTypeSelector: React.FC<{ className="bp3-popover-content-sizing" style={{ maxHeight: '35vh', + maxWidth: '30vw', overflowY: 'auto', }} > diff --git a/src/smart/ui/edit/components/ReferenceSelector.tsx b/src/smart/ui/edit/components/ReferenceSelector.tsx index 57e834c8..03d49664 100644 --- a/src/smart/ui/edit/components/ReferenceSelector.tsx +++ b/src/smart/ui/edit/components/ReferenceSelector.tsx @@ -78,6 +78,7 @@ const SimpleReferenceSelector: React.FC<{ className="bp3-popover-content-sizing" style={{ maxHeight: '35vh', + maxWidth: '30vw', overflowY: 'auto', }} > diff --git a/src/smart/ui/edit/components/RegistrySelector.tsx b/src/smart/ui/edit/components/RegistrySelector.tsx index daf398db..717fa286 100644 --- a/src/smart/ui/edit/components/RegistrySelector.tsx +++ b/src/smart/ui/edit/components/RegistrySelector.tsx @@ -67,6 +67,7 @@ const RegistrySelector: React.FC<{ className="bp3-popover-content-sizing" style={{ maxHeight: '35vh', + maxWidth: '30vw', overflowY: 'auto', }} > diff --git a/src/smart/ui/edit/egateedit.tsx b/src/smart/ui/edit/egateedit.tsx index ee6538ea..798b4595 100644 --- a/src/smart/ui/edit/egateedit.tsx +++ b/src/smart/ui/edit/egateedit.tsx @@ -168,7 +168,10 @@ const EditEGatePage: React.FC<{ onNewID, }; - useEffect(() => setEditing(egate), [egate]); + useEffect(() => { + setEditing(egate); + setEdges([...Object.values(page.edges).filter(e => e.from === id)]); + }, [egate]); useEffect( () => setEdges([...Object.values(page.edges).filter(e => e.from === id)]), [page.edges] diff --git a/src/smart/ui/edit/processedit.tsx b/src/smart/ui/edit/processedit.tsx index 5c9712d8..7c7a5cb8 100644 --- a/src/smart/ui/edit/processedit.tsx +++ b/src/smart/ui/edit/processedit.tsx @@ -321,6 +321,7 @@ const EditProcessPage: React.FC<{ useEffect(() => { setEditing(process); setProvisions(getInitProvisions(model, process)); + setNotes(getInitNotes(model, process)); setMeasurements(getInitMeasurement(process)); }, [process]); diff --git a/src/smart/ui/editFunctions/EditWrapper.tsx b/src/smart/ui/editFunctions/EditWrapper.tsx index c447b960..2b6dd3b5 100644 --- a/src/smart/ui/editFunctions/EditWrapper.tsx +++ b/src/smart/ui/editFunctions/EditWrapper.tsx @@ -59,6 +59,7 @@ const EditWrapper: React.FC<{ const [selected, setSelected] = useState(undefined); const [copied, setCopied] = useState(undefined); const [toaster] = useState(Toaster.create()); + const [isBSI, setIsBSI] = useState(false); const hotkeys = [ { @@ -85,6 +86,12 @@ const EditWrapper: React.FC<{ label: 'Paste', onKeyDown: paste, }, + { + combo: 'ctrl+b', + global: true, + label: 'BSI', + onKeyDown: () => setIsBSI(x => !x), + }, ]; function updateState(newState: EditorState, requireHistory: boolean) { @@ -195,6 +202,7 @@ const EditWrapper: React.FC<{ copy={selected !== undefined ? copy : undefined} paste={copied !== undefined ? paste : undefined} setSelectedId={setSelectedId} + isBSIEnabled={isBSI} /> diff --git a/src/smart/ui/maineditor.tsx b/src/smart/ui/maineditor.tsx index 513ddede..83f5be15 100644 --- a/src/smart/ui/maineditor.tsx +++ b/src/smart/ui/maineditor.tsx @@ -135,6 +135,7 @@ const ModelEditor: React.FC<{ copy?: () => void; paste?: () => void; setSelectedId: (id: string | undefined) => void; + isBSIEnabled?: boolean; }> = ({ isVisible, className, @@ -146,6 +147,7 @@ const ModelEditor: React.FC<{ copy, paste, setSelectedId, + isBSIEnabled, }) => { const { logger } = useContext(DatasetContext); @@ -458,6 +460,7 @@ const ModelEditor: React.FC<{ content={ } diff --git a/src/smart/ui/menu/EditorReferenceMenu.tsx b/src/smart/ui/menu/EditorReferenceMenu.tsx index 95807f81..aca86286 100644 --- a/src/smart/ui/menu/EditorReferenceMenu.tsx +++ b/src/smart/ui/menu/EditorReferenceMenu.tsx @@ -15,8 +15,9 @@ import { indexModel } from '../../model/mapmodel'; const EditorReferenceMenu: React.FC<{ setReference: (x: ReferenceContent | undefined) => void; + isBSIEnabled?: boolean; isCloseEnabled: boolean; -}> = function ({ setReference, isCloseEnabled }) { +}> = function ({ setReference, isBSIEnabled = false, isCloseEnabled }) { const { useDecodedBlob, requestFileFromFilesystem } = useContext(DatasetContext); @@ -61,6 +62,20 @@ const EditorReferenceMenu: React.FC<{ } icon="document-open" /> + {isBSIEnabled && ( + + handleDocumentOpen({ + setDocument: setReference, + useDecodedBlob, + requestFileFromFilesystem, + fileType: FILE_TYPE.BSI, + }) + } + icon="document-open" + /> + )} - setID(x)} /> + setID(removeSpace(x))} + /> {value !== undefined && ( void; useDecodedBlob?: Hooks.UseDecodedBlob; requestFileFromFilesystem?: OpenFileInterface; - fileType: FILE_TYPE.Document | FILE_TYPE.XML; + fileType: FILE_TYPE.Document | FILE_TYPE.XML | FILE_TYPE.BSI; }) { const { setDocument, fileType } = props; handleFileOpen({ @@ -130,7 +137,11 @@ export function handleDocumentOpen(props: { type: fileType, postProcessing: data => setDocument( - fileType === FILE_TYPE.Document ? textToDoc(data) : xmlToDocument(data) + fileType === FILE_TYPE.Document + ? textToDoc(data) + : fileType === FILE_TYPE.XML + ? xmlToDocument(data) + : bsiToDocument(data) ), }); } diff --git a/src/smart/utils/ModelAddComponentHandler.ts b/src/smart/utils/ModelAddComponentHandler.ts index dbd74cbb..d159e184 100644 --- a/src/smart/utils/ModelAddComponentHandler.ts +++ b/src/smart/utils/ModelAddComponentHandler.ts @@ -13,7 +13,7 @@ import { } from '../model/editormodel'; import { ModelWrapper } from '../model/modelwrapper'; import { DataType } from '../serialize/interface/baseinterface'; -import { findUniqueID } from './ModelFunctions'; +import { capitalizeString, findUniqueID, trydefaultID } from './ModelFunctions'; import { NewComponentTypes } from './constants'; import { createApproval, @@ -61,7 +61,11 @@ export function addComponentToModel( } function newProcess(model: EditorModel, title?: string): EditorProcess { - const process = createProcess(findUniqueID('Process', model.elements)); + const id = + title !== undefined + ? trydefaultID(capitalizeString(title), model.elements) + : findUniqueID('Process', model.elements); + const process = createProcess(id); if (title !== undefined) { process.name = title; } diff --git a/src/smart/utils/ModelFunctions.ts b/src/smart/utils/ModelFunctions.ts index 384836ad..fce32a50 100644 --- a/src/smart/utils/ModelFunctions.ts +++ b/src/smart/utils/ModelFunctions.ts @@ -216,6 +216,15 @@ export function findUniqueID(prefix: string, ids: Record) { return name; } +function capital(data: string): string { + return capital.length > 0 ? data[0].toUpperCase() + data.slice(1) : ''; +} + +export function capitalizeString(data: string): string { + const parts = data.split(/\s+/); + return parts.map(x => capital(x)).join(''); +} + export function trydefaultID(name: string, ids: Record) { if (ids[name] !== undefined) { return findUniqueID(name, ids); diff --git a/src/smart/utils/xml/BSIXML.ts b/src/smart/utils/xml/BSIXML.ts new file mode 100644 index 00000000..3d1428ff --- /dev/null +++ b/src/smart/utils/xml/BSIXML.ts @@ -0,0 +1,309 @@ +import React from 'react'; +import { DocSection, DocStatement, MMELDocument } from '../../model/document'; +import { XMLElement } from '../../model/xmlelement'; +import { elementToString, isXMLElement, parseXML } from './XMLParser'; + +function getElementValue(xml: XMLElement, name: string): string { + const array = xml.xmlChild[name]; + if (array === undefined) { + return ''; + } + return elementToString(array[0]); +} + +function getElementValueByPath(xml: XMLElement, path: string[]): string { + for (const p of path) { + const array = xml.xmlChild[p]; + if (array !== undefined && array.length > 0) { + xml = array[0]; + } else { + return ''; + } + } + return elementToString(xml); +} + +function isUnwantedElement(x: XMLElement): boolean { + if (x.tag === 'fn') { + return true; + } + if (x.tag === 'xref' && x.attributes['ref-type'] === 'fn') { + return true; + } + return false; +} + +// remove unwanted elements, e.g., footnote fn +function cleanXML(xml: XMLElement) { + if (xml.xmlChild['fn'] !== undefined) { + delete xml.xmlChild['fn']; + xml.childs = xml.childs.filter( + x => !isXMLElement(x) || !isUnwantedElement(x) + ); + } + for (const c of xml.childs) { + if (isXMLElement(c)) { + cleanXML(c); + } + } +} + +export function bsiToDocument(data: string): MMELDocument { + const xml = parseXML(data); + cleanXML(xml); + const front = xml.xmlChild['front']; + const doc: MMELDocument = { + states: {}, + id: '', + title: '', + sections: [], + type: 'document', + }; + if (front !== undefined && front.length > 0) { + setMeta(doc, front[0]); + } + const body = xml.xmlChild['body']; + if (body !== undefined && body.length > 0) { + setMainDoc(doc, body[0]); + } + return doc; +} + +function setMeta(doc: MMELDocument, xml: XMLElement) { + if (xml !== undefined) { + doc.sdo = getElementValueByPath(xml, ['iso-meta', 'doc-ident', 'sdo']); + if (doc.sdo === '') { + doc.sdo = getElementValueByPath(xml, ['nat-meta', 'doc-ident', 'sdo']); + } + doc.edition = getElementValueByPath(xml, [ + 'iso-meta', + 'std-ident', + 'edition', + ]); + if (doc.edition === '') { + doc.sdo = getElementValueByPath(xml, [ + 'nat-meta', + 'std-ident', + 'edition', + ]); + } + doc.id = + getElementValueByPath(xml, ['iso-meta', 'std-ident', 'originator']) + + getElementValueByPath(xml, ['iso-meta', 'std-ident', 'doc-number']); + if (doc.id === '') { + doc.id = + getElementValueByPath(xml, ['nat-meta', 'std-ident', 'originator']) + + getElementValueByPath(xml, ['nat-meta', 'std-ident', 'doc-number']); + } + doc.title = getElementValueByPath(xml, ['iso-meta', 'title-wrap', 'full']); + if (doc.title === '') { + doc.title = getElementValueByPath(xml, [ + 'nat-meta', + 'title-wrap', + 'full', + ]); + } + } +} + +function setMainDoc(doc: MMELDocument, xml: XMLElement) { + const secs = xml.xmlChild['sec']; + if (secs !== undefined && secs.length > 0) { + for (const sec of secs) { + addSection(doc, sec); + } + } +} + +function addSection(doc: MMELDocument, xml: XMLElement) { + const clause = getElementValue(xml, 'label'); + const title = getElementValue(xml, 'title'); + + const section = createDocSection(clause, title); + doc.sections.push(section); + addStatement(doc, section, title, clause); + + for (const c of xml.childs) { + if (isXMLElement(c)) { + if (c.tag === 'label' || c.tag === 'title') { + // already obtained their values. Ignore these parts + } else if (c.tag === 'sec') { + addSection(doc, c); + } else if (c.tag === 'p') { + addStatement(doc, section, elementToString(c), clause); + } else if (c.tag === 'list') { + processList(doc, section, c, clause); + } else if (c.tag === 'non-normative-note') { + addStatement(doc, section, elementToString(c), clause); + } else if (c.tag === 'ref-list') { + processRefList(doc, section, c, clause); + } else if (c.tag === 'term-sec') { + addTermsSection(doc, c); + } else { + // Other elements are ignored at the moment + // like tables, figures, note, etc + } + } else { + throw new Error('A section shall not have direct text?'); + } + } +} + +function addTermsSection(doc: MMELDocument, xml: XMLElement) { + const clause = getElementValue(xml, 'label'); + const title = getElementValueByPath(xml, [ + 'tbx:termEntry', + 'tbx:langSet', + 'tbx:tig', + 'tbx:term', + ]); + + const section = createDocSection(clause, title); + doc.sections.push(section); + addStatement(doc, section, title, clause); + + const langSet = xml.xmlChild['tbx:termEntry'][0].xmlChild['tbx:langSet'][0]; + const defElement = langSet.xmlChild['tbx:definition']; + if (defElement !== undefined) { + const definition = langSet.xmlChild['tbx:definition'][0]; + + let defText = ''; + for (const c of definition.childs) { + if (isXMLElement(c)) { + if (c.tag === 'list') { + if (defText !== '') { + addStatement(doc, section, defText, clause); + defText = ''; + } + processList(doc, section, c, clause); + } else { + defText += elementToString(c); + } + } else { + defText += c; + } + } + if (defText !== '') { + addStatement(doc, section, defText, clause); + } + + const notes = langSet.xmlChild['tbx:note']; + if (notes !== undefined) { + notes.forEach((note, index) => { + const parts = elementToString(note).split('—'); + parts.forEach((p, pIndex) => { + if (pIndex === 0) { + addStatement( + doc, + section, + `Note ${index + 1} to entry: ${p}`, + clause + ); + } else { + addStatement(doc, section, `—${p}`, clause); + } + }); + }); + } + + const sources = langSet.xmlChild['tbx:source']; + if (sources !== undefined) { + sources.forEach(source => { + addStatement( + doc, + section, + `[SOURCE: ${elementToString(source)}]`, + clause + ); + }); + } + } +} + +function createDocSection(clause: string, title: string): DocSection { + return { + id: clause, + title, + contents: [], + }; +} + +function addStatement( + doc: MMELDocument, + section: DocSection, + statement: string, + clause: string +) { + const st: DocStatement = { + id: Object.values(doc.states).length.toString(), + text: statement, + clause, + uiref: React.createRef(), + paragraph: section.contents.length + 1, + index: 1, + }; + doc.states[st.id] = st; + section.contents.push([st.id]); +} + +function processList( + doc: MMELDocument, + section: DocSection, + xml: XMLElement, + clause: string +) { + for (const c of xml.childs) { + if (isXMLElement(c)) { + if (c.tag === 'list-item') { + const prefix = getElementValue(c, 'label'); + const paras = c.xmlChild['p']; + if (paras !== undefined && paras.length > 0) { + paras.forEach((p, index) => { + if (index === 0) { + addStatement( + doc, + section, + prefix + ' ' + elementToString(p), + clause + ); + } else { + addStatement(doc, section, elementToString(p), clause); + } + }); + } + for (const gc of c.childs) { + if (isXMLElement(gc)) { + if (gc.tag === 'label' || gc.tag === 'p') { + // no problem, already processed + } else if (gc.tag === 'list') { + processList(doc, section, gc, clause); + } else { + // Other elements are ignored at the moment + // like tables, figures, note, etc + } + } else { + throw new Error(`List item contains direct string? ${c}`); + } + } + } else { + throw new Error(`List item contains other elements? ${c.tag}`); + } + } else { + throw new Error(`List contains direct string? ${elementToString(xml)}`); + } + } +} + +function processRefList( + doc: MMELDocument, + section: DocSection, + xml: XMLElement, + clause: string +) { + const refs = xml.xmlChild['ref']; + if (refs !== undefined && refs.length > 0) { + for (const ref of refs) { + addStatement(doc, section, elementToString(ref), clause); + } + } +}