Skip to content

Commit a58dcd5

Browse files
committed
If nodes contain attributes that are defined as a reference property the node is now clickable and will open the referenced file in vscode.
1 parent fffcfb8 commit a58dcd5

File tree

6 files changed

+174
-61
lines changed

6 files changed

+174
-61
lines changed

src/commands/dgmlViewer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ export class DgmlViewer {
2727
case 'saveAsPng':
2828
this.saveAsPng(message.text);
2929
return;
30+
case 'openFile':
31+
const filename = message.text;
32+
if (this.fsUtils.fileExists(filename)) {
33+
console.log('message.text', filename);
34+
var openPath = vscode.Uri.parse("file:///" + filename);
35+
vscode.workspace.openTextDocument(openPath).then(doc => {
36+
vscode.window.showTextDocument(doc);
37+
});
38+
}
39+
return;
3040
}
3141
},
3242
undefined,
@@ -50,7 +60,7 @@ export class DgmlViewer {
5060
const outputJsFilename = DgmlViewer._name + '.js';
5161
let htmlContent = this.generateHtmlContent(webview, outputJsFilename);
5262

53-
this.fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('.', DgmlViewer._name + '.html')), htmlContent, () => { }); // For debugging
63+
// this.fsUtils.writeFile(this.extensionContext?.asAbsolutePath(path.join('.', DgmlViewer._name + '.html')), htmlContent, () => { }); // For debugging
5464
this.fsUtils.writeFile(
5565
this.extensionContext?.asAbsolutePath(path.join('.', outputJsFilename)),
5666
jsContent,

src/dgmlParser.ts

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class DgmlParser {
3838
const children: IXmlNode[] = obj.root.children as IXmlNode[];
3939
children.forEach(xmlNode => {
4040
if (xmlNode.name !== undefined && xmlNode.name.toLowerCase() === 'nodes') {
41-
directedGraph.nodes = this.convertXmlToNodes(xmlNode.children);
41+
directedGraph.nodes = this.convertXmlToNodes(xmlNode.children, filename);
4242
} else if (xmlNode.name !== undefined && xmlNode.name.toLowerCase() === 'links') {
4343
directedGraph.links = this.convertXmlToLinks(xmlNode.children);
4444
} else if (xmlNode.name !== undefined && xmlNode.name.toLowerCase() === 'categories') {
@@ -52,6 +52,7 @@ export class DgmlParser {
5252
this.addStylingToCategories(directedGraph);
5353
this.addCategoryStylingToNodes(directedGraph);
5454
this.addCategoryStylingToLinks(directedGraph);
55+
this.enrichNodes(directedGraph);
5556
}
5657
return directedGraph;
5758
}
@@ -68,47 +69,59 @@ export class DgmlParser {
6869
return dictKeysCopy;
6970
}
7071

71-
private convertXmlToNodes(xmlNodes: IXmlNode[]): Node[] {
72+
private convertXmlToNodes(xmlNodes: IXmlNode[], filename: string): Node[] {
7273
const nodes: Node[] = [];
7374
if (xmlNodes.length > 0) {
7475
xmlNodes.forEach(xmlNode => {
7576
if (xmlNode.attributes !== undefined) {
77+
const newNode = new Node(filename);
7678
const attributesCopy: { [key: string]: string } = this.toLowercaseDictionary(xmlNode.attributes);
77-
const newNode = new Node();
78-
newNode.id = attributesCopy['id'];
79-
newNode.category = attributesCopy['category'];
80-
newNode.description = attributesCopy['description'];
81-
newNode.reference = attributesCopy['reference'];
82-
newNode.isVertical = attributesCopy['isvertical'] !== undefined ? attributesCopy['isvertical'] === 'true' : undefined;
83-
newNode.group = attributesCopy['group'];
84-
newNode.label = attributesCopy['label'];
85-
newNode.visibility = attributesCopy['visibility'];
86-
newNode.background = attributesCopy['background'];
87-
newNode.fontSize = attributesCopy['fontsize'] !== undefined ? +attributesCopy['fontsize'] : undefined;
88-
newNode.fontFamily = attributesCopy['fontfamily'];
89-
newNode.fontStyle = attributesCopy['fontstyle'];
90-
newNode.fontWeight = attributesCopy['fontweight'];
91-
newNode.stroke = attributesCopy['stroke'];
92-
newNode.strokeThickness = attributesCopy['strokethickness'];
93-
newNode.strokeDashArray = attributesCopy['strokedasharray'];
94-
newNode.icon = attributesCopy['icon'];
95-
newNode.shape = attributesCopy['shape'];
96-
newNode.style = attributesCopy['style'];
97-
newNode.horizontalAlignment = attributesCopy['horizontalalignment'];
98-
newNode.verticalAlignment = attributesCopy['verticalalignment'];
99-
newNode.minWidth = attributesCopy['minwidth'] !== undefined ? +attributesCopy['minwidth'] : undefined;
100-
newNode.maxWidth = attributesCopy['maxwidth'] !== undefined ? +attributesCopy['maxwidth'] : undefined;
101-
newNode.nodeRadius = attributesCopy['noderadius'] !== undefined ? +attributesCopy['noderadius'] : undefined;
79+
newNode.id = this.getAttributeValue(attributesCopy, 'id');
80+
newNode.category = this.getAttributeValue(attributesCopy, 'category');
81+
newNode.description = this.getAttributeValue(attributesCopy, 'description');
82+
newNode.reference = this.getAttributeValue(attributesCopy, 'reference');
83+
const isVertical = this.getAttributeValue(attributesCopy, 'isvertical');
84+
newNode.isVertical = isVertical !== undefined ? isVertical === 'true' : undefined;
85+
newNode.group = this.getAttributeValue(attributesCopy, 'group');
86+
newNode.label = this.getAttributeValue(attributesCopy, 'label');
87+
newNode.visibility = this.getAttributeValue(attributesCopy, 'visibility');
88+
newNode.background = this.getAttributeValue(attributesCopy, 'background');
89+
const fontsize = this.getAttributeValue(attributesCopy, 'fontsize');
90+
newNode.fontSize = fontsize !== undefined ? +fontsize : undefined;
91+
newNode.fontFamily = this.getAttributeValue(attributesCopy, 'fontfamily');
92+
newNode.fontStyle = this.getAttributeValue(attributesCopy, 'fontstyle');
93+
newNode.fontWeight = this.getAttributeValue(attributesCopy, 'fontweight');
94+
newNode.stroke = this.getAttributeValue(attributesCopy, 'stroke');
95+
newNode.strokeThickness = this.getAttributeValue(attributesCopy, 'strokethickness');
96+
newNode.strokeDashArray = this.getAttributeValue(attributesCopy, 'strokedasharray');
97+
newNode.icon = this.getAttributeValue(attributesCopy, 'icon');
98+
newNode.shape = this.getAttributeValue(attributesCopy, 'shape');
99+
newNode.style = this.getAttributeValue(attributesCopy, 'style');
100+
newNode.horizontalAlignment = this.getAttributeValue(attributesCopy, 'horizontalalignment');
101+
newNode.verticalAlignment = this.getAttributeValue(attributesCopy, 'verticalalignment');
102+
const minWidth = this.getAttributeValue(attributesCopy, 'minwidth');
103+
newNode.minWidth = minWidth !== undefined ? +minWidth : undefined;
104+
const maxWidth = this.getAttributeValue(attributesCopy, 'maxwidth');
105+
newNode.maxWidth = maxWidth !== undefined ? +maxWidth : undefined;
106+
const nodeRadius = this.getAttributeValue(attributesCopy, 'noderadius');
107+
newNode.nodeRadius = nodeRadius !== undefined ? +nodeRadius : undefined;
102108
if (newNode.category === undefined) {
103109
newNode.category = this.createCategoryRef(xmlNode);
104110
}
105-
if (attributesCopy['bounds'] !== undefined && attributesCopy['bounds'].indexOf(',') !== -1) {
106-
const bounds = attributesCopy['bounds'].split(',');
111+
const boundsValue = this.getAttributeValue(attributesCopy, 'bounds');
112+
if (boundsValue !== undefined && boundsValue.indexOf(',') !== -1) {
113+
const bounds = boundsValue.split(',');
107114
newNode.boundsX = +bounds[0];
108115
newNode.boundsY = +bounds[1];
109116
newNode.boundsWidth = +bounds[2];
110117
newNode.boundsHeight = +bounds[3];
111118
}
119+
const additionalProperties = Object.keys(attributesCopy);
120+
if (additionalProperties.length > 0) {
121+
additionalProperties.forEach(property => {
122+
newNode.properties.push({ id: property, value: attributesCopy[property] });
123+
});
124+
}
112125
if (nodes.filter(n => n.id === newNode.id).length === 0) {
113126
nodes.push(newNode);
114127
}
@@ -118,6 +131,12 @@ export class DgmlParser {
118131
return nodes;
119132
}
120133

134+
private getAttributeValue(attributes: { [key: string]: string }, attributeName: string): string {
135+
const value = attributes[attributeName];
136+
delete attributes[attributeName];
137+
return value;
138+
}
139+
121140
private convertXmlToLinks(xmlNodes: IXmlNode[]): Link[] {
122141
const links: Link[] = [];
123142
if (xmlNodes.length > 0) {
@@ -129,7 +148,7 @@ export class DgmlParser {
129148
newLink.target = attributesCopy['target'];
130149
newLink.label = attributesCopy['label'];
131150
newLink.category = attributesCopy['category'];
132-
newLink.visibility = attributesCopy['visibility'] !== undefined ? attributesCopy['visibility'] === 'hidden' : false;
151+
newLink.visibility = attributesCopy['visibility'] !== undefined ? attributesCopy['visibility'].toLowerCase() === 'hidden' : false;
133152
newLink.background = attributesCopy['background'];
134153
newLink.fontSize = attributesCopy['fontsize'] !== undefined ? +attributesCopy['fontsize'] : undefined;
135154
newLink.fontFamily = attributesCopy['fontfamily'];
@@ -139,7 +158,7 @@ export class DgmlParser {
139158
newLink.strokeThickness = attributesCopy['strokethickness'];
140159
newLink.strokeDashArray = attributesCopy['strokedasharray'];
141160
newLink.seeder = attributesCopy['seeder'] !== undefined ? attributesCopy['seeder'] === 'true' : undefined;
142-
newLink.attractConsumers = attributesCopy['attractconsumers'] !== undefined ? attributesCopy['attractconsumers'] === 'true' : undefined;
161+
newLink.attractConsumers = attributesCopy['attractconsumers'] !== undefined ? attributesCopy['attractconsumers'].toLowerCase() === 'true' : undefined;
143162
if (newLink.category === undefined) {
144163
newLink.category = this.createCategoryRef(xmlNode);
145164
}
@@ -176,8 +195,8 @@ export class DgmlParser {
176195
canBeDataDriven: attributesCopy['canbedatadriven'],
177196
defaultAction: attributesCopy['defaultaction'],
178197
incomingActionLabel: attributesCopy['incomingactionlabel'],
179-
isProviderRoot: attributesCopy['isproviderroot'] !== undefined ? attributesCopy['isproviderroot'] === 'true' : undefined,
180-
isContainment: attributesCopy['iscontainment'] !== undefined ? attributesCopy['iscontainment'] === 'true' : undefined,
198+
isProviderRoot: attributesCopy['isproviderroot'] !== undefined ? attributesCopy['isproviderroot'].toLowerCase() === 'true' : undefined,
199+
isContainment: attributesCopy['iscontainment'] !== undefined ? attributesCopy['iscontainment'].toLowerCase() === 'true' : undefined,
181200
isTag: attributesCopy['istag'] !== undefined ? attributesCopy['istag'] === 'true' : undefined,
182201
navigationActionLabel: attributesCopy['navigationactionlabel'],
183202
outgoingActionLabel: attributesCopy['outgoingactionlabel'],
@@ -220,7 +239,7 @@ export class DgmlParser {
220239
const attributesCopy: { [key: string]: string } = this.toLowercaseDictionary(xmlNode.attributes);
221240
const newProperty = {
222241
id: attributesCopy['id'],
223-
isReference: attributesCopy['isreference'] !== undefined ? attributesCopy['isreference'] === 'true' : undefined,
242+
isReference: attributesCopy['isreference'] !== undefined ? attributesCopy['isreference'].toLowerCase() === 'true' : undefined,
224243
label: attributesCopy['label'],
225244
dataType: attributesCopy['datatype'],
226245
description: attributesCopy['description'],
@@ -242,7 +261,7 @@ export class DgmlParser {
242261
const attributesCopy: { [key: string]: string } = this.toLowercaseDictionary(xmlNode.attributes);
243262
const newProperty = {
244263
targetType: attributesCopy['targettype'],
245-
isEnabled: attributesCopy['isenabled'] !== undefined ? attributesCopy['isenabled'] === 'true' : undefined,
264+
isEnabled: attributesCopy['isenabled'] !== undefined ? attributesCopy['isenabled'].toLowerCase() === 'true' : undefined,
246265
groupLabel: attributesCopy['grouplabel'],
247266
valueLabel: attributesCopy['valuelabel'],
248267
toolTip: attributesCopy['tooltip'],
@@ -388,4 +407,17 @@ export class DgmlParser {
388407
});
389408
}
390409
}
410+
411+
private enrichNodes(directedGraph: IDirectedGraph): void {
412+
directedGraph.properties.forEach(property => {
413+
directedGraph.nodes.forEach(node => {
414+
if (node.properties.length > 0) {
415+
const existingPropertyIdx = node.properties.findIndex(nodeProperty => nodeProperty.id.toLowerCase() === property.id.toLowerCase());
416+
if (existingPropertyIdx !== -1) {
417+
Object.assign(node.properties[existingPropertyIdx], property);
418+
}
419+
}
420+
});
421+
});
422+
}
391423
}

src/filesystemUtils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ export class FileSystemUtils {
2525
return files;
2626
}
2727

28+
public fileExists(filename: string): boolean {
29+
try{
30+
return fs.lstatSync(filename).isFile();
31+
} catch {
32+
return false;
33+
}
34+
}
2835
private isDirectory(directoryName: any): boolean {
2936
return fs.lstatSync(directoryName).isDirectory();
3037
};

src/model/IProperty.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// https://schemas.microsoft.com/vs/2009/dgml/dgml.xsd
22
export interface IProperty {
33
id: string;
4-
isReference: boolean;
5-
label: string;
6-
dataType: string;
7-
description: string;
8-
group: string;
9-
referenceTemplate: string;
4+
value?: string;
5+
isReference?: boolean;
6+
label?: string;
7+
dataType?: string;
8+
description?: string;
9+
group?: string;
10+
referenceTemplate?: string;
1011
}

src/model/Node.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { ICategory } from "@model";
1+
import { ICategory, IProperty } from "@model";
2+
import path = require("path");
3+
import { FileSystemUtils } from "@src";
24
import { BaseElement } from "./BaseElement";
35

46
// https://schemas.microsoft.com/vs/2009/dgml/dgml.xsd
57
export class Node extends BaseElement {
8+
public filename: string;
69
public id: string | undefined;
710
public category: string | undefined;
811
public description: string | undefined;
@@ -41,13 +44,20 @@ export class Node extends BaseElement {
4144
this.categoryRef = categoryRef;
4245
}
4346

47+
public properties: IProperty[] = [];
48+
49+
constructor(filename: string) {
50+
super();
51+
this.filename = filename;
52+
}
53+
4454
public toJsString(): string {
4555
const jsStringProperties: string[] = [];
4656
if (this.id !== undefined) { jsStringProperties.push(`id: "${this.id}"`); }
4757
let label = this.convertNewlines(this.label);
4858
if (this.fontWeight !== undefined &&
4959
this.fontWeight.toLowerCase().startsWith('bold')) {
50-
label = `<b>${label}</b>`;
60+
label = `<b>${label}</b>`;
5161
}
5262
const description = this.convertNewlines(this.description);
5363
if (this.description !== undefined) { jsStringProperties.push(`title: "${description}"`); }
@@ -66,16 +76,16 @@ export class Node extends BaseElement {
6676
}
6777
if (this.stroke === undefined &&
6878
this.categoryRef.stroke !== undefined) {
69-
jsStringColorProperties.push(`border: "${this.convertColorValue(this.categoryRef.stroke)}"`);
79+
jsStringColorProperties.push(`border: "${this.convertColorValue(this.categoryRef.stroke)}"`);
7080
}
7181
if (this.strokeThickness === undefined &&
7282
this.categoryRef.strokeThickness !== undefined) {
7383
jsStringProperties.push(`borderWidth: "${this.convertColorValue(this.categoryRef.strokeThickness)}"`);
7484
}
7585
if (this.strokeDashArray === undefined &&
7686
this.categoryRef.strokeDashArray !== undefined) {
77-
jsStringProperties.push(`shapeProperties: { borderDashes: true }`);
78-
}
87+
jsStringProperties.push(`shapeProperties: { borderDashes: true }`);
88+
}
7989
if (this.fontFamily === undefined &&
8090
this.categoryRef.fontFamily !== undefined) {
8191
jsStringFontProperties.push(`face: ${this.categoryRef.fontFamily}`);
@@ -87,7 +97,7 @@ export class Node extends BaseElement {
8797
if (this.fontWeight === undefined &&
8898
this.categoryRef.fontWeight !== undefined &&
8999
this.categoryRef.fontWeight.toLowerCase().startsWith('bold')) {
90-
label = `<b>${label}</b>`;
100+
label = `<b>${label}</b>`;
91101
}
92102
}
93103
if (this.label !== undefined) {
@@ -103,6 +113,23 @@ export class Node extends BaseElement {
103113
if (this.boundsX !== undefined && this.boundsY !== undefined) { jsStringProperties.push(`x: ${this.boundsX}, y: ${this.boundsY}, fixed: { x: true, y: true}`); }
104114
if (this.boundsWidth !== undefined) { jsStringProperties.push(`widthConstraint: { minimum: ${this.boundsWidth} }`); }
105115
if (this.boundsHeight !== undefined) { jsStringProperties.push(`heightConstraint: { minimum: ${this.boundsHeight}, valign: top }`); }
116+
if (this.properties.length > 0) {
117+
const referenceProperties = this.properties.find(property => property.isReference === true);
118+
if (referenceProperties !== undefined && referenceProperties.value !== undefined) {
119+
const fsUtil = new FileSystemUtils();
120+
if (fsUtil.fileExists(referenceProperties.value)) {
121+
let referenceFilename = referenceProperties.value.split('\\').join('/');
122+
jsStringProperties.push(`filepath: "${referenceFilename}"`);
123+
} else {
124+
const currentDocumentFolder = path.dirname(this.filename);
125+
let referenceFilename = path.join(currentDocumentFolder, referenceProperties.value);
126+
if (fsUtil.fileExists(referenceFilename)) {
127+
referenceFilename = referenceFilename.split('\\').join('/');
128+
jsStringProperties.push(`filepath: "${referenceFilename}"`);
129+
}
130+
}
131+
}
132+
}
106133
return `{${jsStringProperties.join(', ')}}`;
107134
}
108135

0 commit comments

Comments
 (0)