Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ <h3>{{ record.dataSet }}</h3>
</div>
<div class="mb-3">
<button (click)="isEditing = true" class="btn btn--sm btn--primary">Edit</button>
<a class="btn btn--sm btn--secondary ms-2" [download]="record.id + '.json'" [href]="'/api/interfaces/' + record.id + '/export'"
>Export</a
>
</div>
<div class="table-responsive">
<table class="table">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@
<div class="mb-3">
<shared-text-field id="customId" label="Custom ID" [source]="record" [target]="record" [error]="error"></shared-text-field>
</div>
<div class="mb-3">
<shared-text-field id="name" label="Name" [source]="record" [target]="record" [error]="error"></shared-text-field>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ <h1 class="h2">Edit Screen</h1>
<label>Name</label>
<h3>{{ record.name }}</h3>
</div>
<div class="mb-3">
<label>Position</label>
<h3>{{ record.position }}</h3>
</div>
<div class="mb-3">
<button (click)="isEditing = true" class="btn btn--sm btn--primary">Edit</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<div class="mb-3">
<shared-text-field id="name" label="Name" [source]="record" [target]="record" [error]="error"></shared-text-field>
</div>
<div class="mb-3">
<shared-text-field type="number" id="position" label="Position" [source]="record" [target]="record" [error]="error"></shared-text-field>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ <h4>National Repository</h4>
</ng-template>
<ng-template #item let-version="item">
<div class="d-flex justify-content-between">
{{ version }}
<a style="font-size: inherit" *ngIf="nemsis.versionsInstalled.includes(version)" [routerLink]="['/nemsis', version]">{{
version
}}</a>
<span *ngIf="!nemsis.versionsInstalled.includes(version)">{{ version }}</span>
<span *ngIf="nemsis.versionsInstalled.includes(version)">
<i class="fas fa-check-circle text-success"></i>
</span>
Expand Down
122 changes: 79 additions & 43 deletions lib/nemsis/emsXsdParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,121 @@ const path = require('path');
const { XmlParser } = require('./xmlParser');

class NemsisEmsXsdParser extends XmlParser {
onStartElement(name, attr) {
if (name === 'xs:include') {
getCurrentXsdPath() {
return `/${this.stack
.map((s) => {
let component = s.element;
if (s.attr.id) {
component += `[@id='${s.attr.id}']`;
} else if (s.attr.name) {
component += `[@name='${s.attr.name}']`;
}
return component;
})
.join('/')}`;
}

getCurrentXmlPath() {
return (
this.xmlXPathPrefix +
this.nodes.map((n) => `${n.name}${n.maxOccurs === null || (Number.isInteger(n.maxOccurs) && n.maxOccurs > 1) ? `[]` : ''}`).join('/')
);
}

onStartElement(element, attr) {
this.stack.push({ xsd: this.xsd, element, attr });
if (element === 'xs:include') {
this.includes.push(attr.schemaLocation);
}
if (this.stack.length === 0) {
} else if (this.nodes.length === 0) {
// watch for start element
if (attr.name.startsWith(this.startElement.name)) {
this.stack.push(this.startElement);
if (attr.name?.startsWith(this.startingNode.name)) {
this.nodes.push(this.startingNode);
this.stack[this.stack.length - 1].node = this.startingNode;
this.startingNode.xsdPath = this.getCurrentXsdPath();
this.startingNode.xmlPath = this.getCurrentXmlPath();
}
} else if (name === 'xs:element') {
} else if (element === 'xs:element') {
const minOccurs = parseInt(attr.minOccurs ?? '1', 10);
const maxOccurs = parseInt(attr.maxOccurs ?? '1', 10);
const node = {
name: attr.name,
xsd: this.xsd,
name: attr.name,
minOccurs,
maxOccurs: Number.isNaN(maxOccurs) ? null : maxOccurs,
xsdPath: '',
xmlPath: '',
children: [],
};
if (attr.nillable) {
node.isNillable = attr.nillable === 'true';
}
for (let i = this.stack.length - 1; i >= 0; i -= 1) {
if (this.stack[i].name) {
this.stack[i].children.push(node);
for (let i = this.nodes.length - 1; i >= 0; i -= 1) {
if (this.nodes[i].name) {
this.nodes[i].children?.push(node);
break;
}
}
this.stack.push(node);
} else {
this.stack.push({ attr });
this.nodes.push(node);
this.stack[this.stack.length - 1].node = node;
node.xsdPath = this.getCurrentXsdPath();
node.xmlPath = this.getCurrentXmlPath();
}
}

onEndElement(name) {
if (this.stack.length > 0) {
const { _text } = this.stack[this.stack.length - 1];
let element;
for (let i = this.stack.length - 2; i >= 0; i -= 1) {
if (this.stack[i].name) {
element = this.stack[i];
break;
}
}
if ((name === 'definition' || name === 'xs:documentation') && element && _text) {
element.definition = _text;
} else if (name === 'name' && element && _text) {
element.displayName = _text;
} else if (name === 'national' && element) {
element.isNational = _text === 'Yes';
} else if (name === 'state' && element) {
element.isState = _text === 'Yes';
} else if (name === 'usage' && element) {
element.usage = _text;
}
delete this.stack[this.stack.length - 1]._text;
this.stack.pop();
if (this.stack.length === 0) {
this.resolve();
onEndElement(element) {
const { _text } = this.stack[this.stack.length - 1];
let node;
for (let i = this.stack.length - 2; i >= 0; i -= 1) {
if (this.stack[i].node) {
({ node } = this.stack[i]);
break;
}
}
if ((element === 'definition' || element === 'xs:documentation') && node && _text) {
node.definition = _text;
} else if (element === 'name' && node && _text) {
node.displayName = _text;
} else if (element === 'national' && node) {
node.isNational = _text === 'Yes';
} else if (element === 'state' && node) {
node.isState = _text === 'Yes';
} else if (element === 'usage' && node) {
node.usage = _text;
}
if (this.stack[this.stack.length - 1].node) {
this.nodes.pop();
}
this.stack.pop();
if (this.stack.length === 0) {
this.resolve();
}
}

async parsePatientCareReport() {
this.includes = [];
this.nodes = [];
this.xsd = path.basename(this.filePath);
this.result = { xsd: this.xsd, name: 'PatientCareReport', children: [] };
this.startElement = this.result;
this.result = {
xsd: this.xsd,
name: 'PatientCareReport',
xsdPath: '',
xmlPath: '',
children: [],
};
this.startingNode = this.result;
this.xmlXPathPrefix = '/';
await this.parse((_, parser) => {
parser.on('startElement', (name, attr) => this.onStartElement(name, attr));
parser.on('endElement', (name) => this.onEndElement(name));
});
this.xmlXPathPrefix = '/PatientCareReport/';
for (const child of this.result.children) {
const xsd = this.includes.find((i) => i.startsWith(child.name));
if (xsd) {
this.filePath = path.resolve(path.dirname(this.filePath), xsd);
this.xsd = xsd;
this.startElement = child;
this.startElement.xsd = xsd;
this.startingNode = child;
this.startingNode.xsd = xsd;
// eslint-disable-next-line no-await-in-loop
await this.parse((_, parser) => {
parser.on('startElement', (name, attr) => this.onStartElement(name, attr));
Expand Down
45 changes: 45 additions & 0 deletions migrations/20250715014215-fix-interface-screens.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.dropTable('interface_screens');
await queryInterface.addColumn('screens', 'position', {
type: Sequelize.INTEGER,
});
await queryInterface.sequelize.query('ALTER TABLE "screens" ALTER COLUMN "interface_id" SET NOT NULL;');
},

async down(queryInterface, Sequelize) {
await queryInterface.sequelize.query('ALTER TABLE "screens" ALTER COLUMN "interface_id" DROP NOT NULL;');
await queryInterface.removeColumn('screens', 'position');
await queryInterface.createTable('interface_screens', {
id: {
allowNull: false,
defaultValue: Sequelize.literal('gen_random_uuid()'),
primaryKey: true,
type: Sequelize.UUID,
},
interface_id: {
type: Sequelize.UUID,
references: {
model: {
tableName: 'interfaces',
},
key: 'id',
},
},
screen_id: {
type: Sequelize.UUID,
references: {
model: {
tableName: 'screens',
},
key: 'id',
},
},
position: {
type: Sequelize.UUID,
},
});
await queryInterface.addIndex('interface_screens', ['interface_id', 'screen_id'], { unique: true });
},
};
12 changes: 12 additions & 0 deletions migrations/20260329001859-add-name-to-section-elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('section_elements', 'name', {
type: Sequelize.STRING,
});
},

async down(queryInterface, Sequelize) {
await queryInterface.removeColumn('section_elements', 'name');
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('nemsis_elements', 'xsd_path', {
type: Sequelize.TEXT,
allowNull: true,
});
await queryInterface.addColumn('nemsis_elements', 'xml_path', {
type: Sequelize.TEXT,
allowNull: true,
});
},

async down(queryInterface, Sequelize) {
await queryInterface.removeColumn('nemsis_elements', 'xsd_path');
await queryInterface.removeColumn('nemsis_elements', 'xml_path');
},
};
3 changes: 1 addition & 2 deletions models/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ module.exports = (sequelize, DataTypes) => {
static associate(models) {
Interface.belongsTo(models.User, { as: 'createdBy' });
Interface.belongsTo(models.User, { as: 'updatedBy' });
Interface.hasMany(models.InterfaceScreen, { as: 'interfaceScreens' });
Interface.hasMany(models.Screen, { as: 'screens' });
Interface.hasMany(models.Screen, { as: 'screens', foreignKey: 'interfaceId' });
}
}
Interface.init(
Expand Down
22 changes: 0 additions & 22 deletions models/interfaceScreen.js

This file was deleted.

2 changes: 2 additions & 0 deletions models/nemsisElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ module.exports = (sequelize, DataTypes) => {
nemsisVersion: DataTypes.STRING,
dataSet: DataTypes.STRING,
xsd: DataTypes.STRING,
xsdPath: DataTypes.TEXT,
xmlPath: DataTypes.TEXT,
name: DataTypes.STRING,
displayName: DataTypes.STRING,
definition: DataTypes.TEXT,
Expand Down
4 changes: 2 additions & 2 deletions models/screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ module.exports = (sequelize, DataTypes) => {
class Screen extends Model {
static associate(models) {
Screen.belongsTo(models.Interface, { as: 'interface' });
Screen.hasOne(models.InterfaceScreen, { as: 'interfaceScreen' });
Screen.belongsTo(models.User, { as: 'createdBy' });
Screen.belongsTo(models.User, { as: 'updatedBy' });
Screen.hasMany(models.Section, { as: 'sections' });
Screen.hasMany(models.Section, { as: 'sections', foreignKey: 'screenId' });
}
}
Screen.init(
{
name: DataTypes.STRING,
position: DataTypes.INTEGER,
},
{
sequelize,
Expand Down
2 changes: 1 addition & 1 deletion models/section.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = (sequelize, DataTypes) => {
Section.belongsTo(models.Screen, { as: 'screen' });
Section.belongsTo(models.User, { as: 'createdBy' });
Section.belongsTo(models.User, { as: 'updatedBy' });
Section.hasMany(models.SectionElement, { as: 'elements' });
Section.hasMany(models.SectionElement, { as: 'elements', foreignKey: 'sectionId' });
}
}
Section.init(
Expand Down
1 change: 1 addition & 0 deletions models/sectionElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = (sequelize, DataTypes) => {
position: DataTypes.INTEGER,
column: DataTypes.INTEGER,
customId: DataTypes.STRING,
name: DataTypes.STRING,
},
{
sequelize,
Expand Down
Loading
Loading