UNPKG

vsdx-js

Version:

typescript library to parse a Visio (.vsdx) file into a javascript object

254 lines (253 loc) 10.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseVisioFile = parseVisioFile; /* eslint-disable @typescript-eslint/no-explicit-any */ const adm_zip_1 = __importDefault(require("adm-zip")); const fs = __importStar(require("fs")); const xml2js_1 = require("xml2js"); const jsonObjects = {}; let masters = []; let pages = []; let relationships = []; let stylesheets = []; async function parseVisioFile(filePath) { const vsdxBuffer = fs.readFileSync(filePath); const archive = new adm_zip_1.default(vsdxBuffer); const entries = archive.getEntries(); for (const entry of entries) { let jsonObj = undefined; if (entry.entryName.endsWith('.rels')) { const xmlContent = entry.getData().toString('utf-8'); jsonObj = await (0, xml2js_1.parseStringPromise)(xmlContent); relationships.push.apply(relationships, parseRelationships(jsonObj)); } if (entry.entryName.endsWith('.xml')) { const xmlContent = entry.getData().toString('utf-8'); jsonObj = await (0, xml2js_1.parseStringPromise)(xmlContent); const fileName = getEntryName(entry.entryName); switch (fileName) { case 'document': stylesheets = parseDocumentProperties(jsonObj); break; case 'masters': masters = parseMastersFile(jsonObj); break; case 'pages': pages = parsePagesFile(jsonObj); break; default: jsonObjects[fileName] = jsonObj; } } } for (const page of pages) { const rel = relationships.find((relation) => relation.Id === page.RelationshipId && relation.Type === 'Page'); if (rel) { const entryName = getEntryName(rel.Target); const pageObject = jsonObjects[entryName]; page.Shapes = getShapes(pageObject); } } return { Masters: masters, Pages: pages, Stylesheets: stylesheets, Relationships: relationships }; } const parseRelationships = (jsonObj) => { const entries = []; const relObjects = jsonObj['Relationships']['Relationship']; for (let i = 0; i < relObjects.length; i++) { const relationship = {}; relationship.Id = relObjects[i]['$']['Id']; relationship.Target = relObjects[i]['$']['Target']; const type = getRelationshipType(relObjects[i]['$']['Type']); if (type) { relationship.Type = type; } entries.push(relationship); } return entries; }; const parseDocumentProperties = (jsonObj) => { const styleSheets = []; const stylesheetObjects = jsonObj['VisioDocument']['StyleSheets'][0]['StyleSheet']; for (let i = 0; i < stylesheetObjects.length; i++) { const sheet = {}; sheet.ID = stylesheetObjects[i]['$']['ID']; sheet.Name = stylesheetObjects[i]['$']['Name']; sheet.LineStyleRefId = stylesheetObjects[i]['$']['LineStyle']; sheet.FillStyleRefId = stylesheetObjects[i]['$']['FillStyle']; sheet.TextStyleRefId = stylesheetObjects[i]['$']['TextStyle']; sheet.Style = stylesheetObjects[i]['Cell']; styleSheets.push(sheet); } return styleSheets; }; const parseMastersFile = (jsonObj) => { const masters = []; const masterObjects = jsonObj['Masters']['Master']; for (let i = 0; i < masterObjects.length; i++) { const master = {}; master.Id = masterObjects[i]['$']['ID']; master.Name = masterObjects[i]['$']['Name']; master.UniqueID = masterObjects[i]['$']['UniqueID']; master.MasterType = masterObjects[i]['$']['MasterType']; master.RelationshipId = masterObjects[i]['Rel'][0]['$']['r:id']; master.Hidden = masterObjects[i]['$']['Hidden']; master.LineStyleRefId = masterObjects[i]['PageSheet']['LineStyle']; master.FillStyleRefId = masterObjects[i]['PageSheet']['FillStyle']; master.TextStyleRefId = masterObjects[i]['PageSheet']['TextStyle']; masters.push(master); } return masters; }; const parsePagesFile = (jsonObj) => { const pages = []; const objects = jsonObj['Pages']['Page']; for (let i = 0; i < objects.length; i++) { const page = {}; page.Id = objects[i]['$']['ID']; page.Name = objects[i]['$']['Name']; page.RelationshipId = objects[i]['Rel'][0]['$']['r:id']; pages.push(page); } return pages; }; const getShapes = (pageObject) => { let shapes = []; const shapeObjects = pageObject['PageContents']['Shapes'][0]; const connectObjects = pageObject['PageContents']['Connects']; try { const shapeCount = shapeObjects['Shape'].length; for (let i = 0; i < shapeCount; i++) { const shape = { Type: 'unknown', IsEdge: false, Label: '' }; const shapeContainer = shapeObjects['Shape'][i]; const cells = shapeContainer['Cell']; shape.Id = shapeContainer['$']['ID']; shape.MasterId = shapeContainer['$']['Master']; const master = masters.find((master) => master.Id === shape.MasterId); if (master) { shape.Type = master.Name; const masterRel = relationships.find((relation) => relation.Id === master.RelationshipId && relation.Type === 'Master'); if (masterRel) { const masterObj = jsonObjects[getEntryName(masterRel?.Target)]; if (shape.Type === 'Dynamic connector' && connectObjects) { const { fromNode, toNode } = getConnectorNodes(connectObjects[0]['Connect'], shape.Id); shape.FromNode = fromNode; shape.ToNode = toNode; shape.IsEdge = true; } } } if (shapeContainer['Text'] && shapeContainer['Text'][0]) { shape.Label = shapeContainer['Text'][0]['_'].replace(/\r?\n|\r/g, '').trim(); } shape.Style = getStyleFromObject(cells); shapes.push(shape); } } catch (e) { console.log(e); } return shapes; }; const getConnectorNodes = (connectObjects, shapeId) => { let fromNode = ''; let toNode = ''; try { const connects = connectObjects.filter( // @ts-ignore (connect) => connect['$'].FromSheet === shapeId); // @ts-ignore const from = connects.find((c) => c['$'].FromCell === 'BeginX')['$']; // @ts-ignore const to = connects.find((c) => c['$'].FromCell === 'EndX')['$']; fromNode = from.ToSheet; toNode = to.ToSheet; } catch (e) { console.log(e); } return { fromNode, toNode }; }; const getStyleFromObject = (cells) => { const style = {}; const lineWeightInPixels = parseFloat(getValueFromCell(cells, 'LineWeight')) * 96; style.LineWeight = lineWeightInPixels; style.LineColor = getValueFromCell(cells, 'LineColor'); style.LinePattern = parseFloat(getValueFromCell(cells, 'LinePattern')); style.Rounding = parseFloat(getValueFromCell(cells, 'Rounding')); style.BeginArrow = parseFloat(getValueFromCell(cells, 'BeginArrow')); style.BeginArrowSize = parseFloat(getValueFromCell(cells, 'BeginArrowSize')); style.EndArrow = parseFloat(getValueFromCell(cells, 'EndArrow')); style.EndArrowSize = parseFloat(getValueFromCell(cells, 'EndArrowSize')); style.LineCap = parseFloat(getValueFromCell(cells, 'LineCap')); style.FillForeground = getValueFromCell(cells, 'FillForegnd'); style.FillBackground = getValueFromCell(cells, 'FillBkgnd'); style.TextColor = getValueFromCell(cells, 'Color'); style.FillPattern = parseFloat(getValueFromCell(cells, 'FillPattern')); return style; }; const getValueFromCell = (cells, field) => { let value = ''; for (let i = 0; i < cells.length; i++) { const cell = cells[i]; if (cell['$']['N'] === field) { value = cell['$']['V']; } } return value; }; const getRelationshipType = (type) => { const index = type.lastIndexOf('/') + 1; switch (type.substring(index)) { case 'master': return 'Master'; case 'page': return 'Page'; default: return undefined; } }; const getEntryName = (entryName) => { const nameStartIndex = entryName.lastIndexOf('/') + 1; return entryName.substring(nameStartIndex).replace('.xml', ''); };