/**
* Edit this
* Page[[N|Solid](img/GitHub-Mark-32px.png)](https://github.com/mermaid-js/mermaid/blob/develop/src/mermaidAPI.js)
*
* This is the API to be used when optionally handling the integration with the web page, instead of
* using the default integration provided by mermaid.js.
*
* The core of this api is the [**render**](Setup.md?id=render) function which, given a graph
* definition as text, renders the graph/diagram and returns an svg element for the graph.
*
* It is is then up to the user of the API to make use of the svg, either insert it somewhere in the
* page or do something completely different.
*
* In addition to the render function, a number of behavioral configuration options are available.
*
* @name mermaidAPI
*/
import { select } from 'd3';
import { compile, serialize, stringify } from 'stylis';
import pkg from '../package.json';
import * as configApi from './config';
import classDb from './diagrams/class/classDb';
import classRenderer from './diagrams/class/classRenderer';
import classRendererV2 from './diagrams/class/classRenderer-v2';
import classParser from './diagrams/class/parser/classDiagram';
import erDb from './diagrams/er/erDb';
import erRenderer from './diagrams/er/erRenderer';
import erParser from './diagrams/er/parser/erDiagram';
import flowDb from './diagrams/flowchart/flowDb';
import flowRenderer from './diagrams/flowchart/flowRenderer';
import flowRendererV2 from './diagrams/flowchart/flowRenderer-v2';
import flowParser from './diagrams/flowchart/parser/flow';
import ganttDb from './diagrams/gantt/ganttDb';
import ganttRenderer from './diagrams/gantt/ganttRenderer';
import ganttParser from './diagrams/gantt/parser/gantt';
import gitGraphAst from './diagrams/git/gitGraphAst';
import gitGraphRenderer from './diagrams/git/gitGraphRenderer';
import gitGraphParser from './diagrams/git/parser/gitGraph';
import infoDb from './diagrams/info/infoDb';
import infoRenderer from './diagrams/info/infoRenderer';
import infoParser from './diagrams/info/parser/info';
import pieParser from './diagrams/pie/parser/pie';
import pieDb from './diagrams/pie/pieDb';
import pieRenderer from './diagrams/pie/pieRenderer';
import requirementParser from './diagrams/requirement/parser/requirementDiagram';
import requirementDb from './diagrams/requirement/requirementDb';
import requirementRenderer from './diagrams/requirement/requirementRenderer';
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram';
import sequenceDb from './diagrams/sequence/sequenceDb';
import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
import stateParser from './diagrams/state/parser/stateDiagram';
import stateDb from './diagrams/state/stateDb';
import stateRenderer from './diagrams/state/stateRenderer';
import stateRendererV2 from './diagrams/state/stateRenderer-v2';
import journeyDb from './diagrams/user-journey/journeyDb';
import journeyRenderer from './diagrams/user-journey/journeyRenderer';
import journeyParser from './diagrams/user-journey/parser/journey';
import errorRenderer from './errorRenderer';
import { attachFunctions } from './interactionDb';
import { log, setLogLevel } from './logger';
import getStyles from './styles';
import theme from './themes';
import utils, { directiveSanitizer, assignWithDepth, sanitizeCss } from './utils';
/**
* @param text
* @returns {any}
*/
function parse(text) {
const cnf = configApi.getConfig();
const graphInit = utils.detectInit(text, cnf);
if (graphInit) {
reinitialize(graphInit);
log.debug('reinit ', graphInit);
}
const graphType = utils.detectType(text, cnf);
let parser;
log.debug('Type ' + graphType);
switch (graphType) {
case 'git':
parser = gitGraphParser;
parser.parser.yy = gitGraphAst;
break;
case 'flowchart':
flowDb.clear();
parser = flowParser;
parser.parser.yy = flowDb;
break;
case 'flowchart-v2':
flowDb.clear();
parser = flowParser;
parser.parser.yy = flowDb;
break;
case 'sequence':
parser = sequenceParser;
parser.parser.yy = sequenceDb;
break;
case 'gantt':
parser = ganttParser;
parser.parser.yy = ganttDb;
break;
case 'class':
parser = classParser;
parser.parser.yy = classDb;
break;
case 'classDiagram':
parser = classParser;
parser.parser.yy = classDb;
break;
case 'state':
parser = stateParser;
parser.parser.yy = stateDb;
break;
case 'stateDiagram':
parser = stateParser;
parser.parser.yy = stateDb;
break;
case 'info':
log.debug('info info info');
parser = infoParser;
parser.parser.yy = infoDb;
break;
case 'pie':
log.debug('pie');
parser = pieParser;
parser.parser.yy = pieDb;
break;
case 'er':
log.debug('er');
parser = erParser;
parser.parser.yy = erDb;
break;
case 'journey':
log.debug('Journey');
parser = journeyParser;
parser.parser.yy = journeyDb;
break;
case 'requirement':
case 'requirementDiagram':
log.debug('RequirementDiagram');
parser = requirementParser;
parser.parser.yy = requirementDb;
break;
}
parser.parser.yy.graphType = graphType;
parser.parser.yy.parseError = (str, hash) => {
const error = { str, hash };
throw error;
};
parser.parse(text);
return parser;
}
export const encodeEntities = function (text) {
let txt = text;
txt = txt.replace(/style.*:\S*#.*;/g, function (s) {
const innerTxt = s.substring(0, s.length - 1);
return innerTxt;
});
txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) {
const innerTxt = s.substring(0, s.length - 1);
return innerTxt;
});
txt = txt.replace(/#\w+;/g, function (s) {
const innerTxt = s.substring(1, s.length - 1);
const isInt = /^\+?\d+$/.test(innerTxt);
if (isInt) {
return 'fl°°' + innerTxt + '¶ß';
} else {
return 'fl°' + innerTxt + '¶ß';
}
});
return txt;
};
export const decodeEntities = function (text) {
let txt = text;
txt = txt.replace(/fl°°/g, function () {
return '';
});
txt = txt.replace(/fl°/g, function () {
return '&';
});
txt = txt.replace(/¶ß/g, function () {
return ';';
});
return txt;
};
/**
* Function that renders an svg with a graph from a chart definition. Usage example below.
*
* ```javascript
* mermaidAPI.initialize({
* startOnLoad: true,
* });
* $(function () {
* const graphDefinition = 'graph TB\na-->b';
* const cb = function (svgGraph) {
* console.log(svgGraph);
* };
* mermaidAPI.render('id1', graphDefinition, cb);
* });
* ```
*
* @param {any} id The id of the element to be rendered
* @param {any} _txt The graph definition
* @param {any} cb Callback which is called after rendering is finished with the svg code as inparam.
* @param {any} container Selector to element in which a div with the graph temporarily will be
* inserted. In one is provided a hidden div will be inserted in the body of the page instead. The
* element will be removed when rendering is completed.
* @returns {any}
*/
const render = function (id, _txt, cb, container) {
configApi.reset();
let txt = _txt;
const graphInit = utils.detectInit(txt);
if (graphInit) {
directiveSanitizer(graphInit);
configApi.addDirective(graphInit);
}
let cnf = configApi.getConfig();
// Check the maximum allowed text size
if (_txt.length > cnf.maxTextSize) {
txt = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
}
// let d3Iframe;
let root = select('body');
// In regular execurtion the container will be the div with a mermaid class
if (typeof container !== 'undefined') {
if (cnf.securityLevel === 'sandbox') {
// IF we are in sandboxed mode, we do everyting mermaid related
// in a sandboxed div
const iframe = select('body')
.append('iframe')
.attr('id', 'i' + id)
.attr('style', 'width: 100%; height: 100%;')
.attr('sandbox', '');
// const iframeBody = ;
root = select(iframe.nodes()[0].contentDocument.body);
root.node().style.margin = 0;
}
// A container was provided by the caller
container.innerHTML = '';
if (cnf.securityLevel === 'sandbox') {
// IF we are in sandboxed mode, we do everyting mermaid related
// in a sandboxed div
const iframe = select(container)
.append('iframe')
.attr('id', 'i' + id)
.attr('style', 'width: 100%; height: 100%;')
.attr('sandbox', '');
// const iframeBody = ;
root = select(iframe.nodes()[0].contentDocument.body);
root.node().style.margin = 0;
} else {
root = select(container);
}
root
.append('div')
.attr('id', 'd' + id)
.attr('style', 'font-family: ' + cnf.fontFamily)
.append('svg')
.attr('id', id)
.attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g');
} else {
// No container was provided
// If there is an existsing element with the id, we remove it
// this likley a previously rendered diagram
const existingSvg = document.getElementById(id);
if (existingSvg) {
existingSvg.remove();
}
// Remove previous tpm element if it exists
let element;
if (cnf.securityLevel !== 'sandbox') {
element = document.querySelector('#' + 'd' + id);
} else {
element = document.querySelector('#' + 'i' + id);
}
if (element) {
element.remove();
}
// if (cnf.securityLevel === 'sandbox') {
// const iframe = select('body')
// .append('iframe')
// .attr('id', 'i' + id)
// .attr('sandbox', '');
// // const iframeBody = ;
// root = select(iframe.nodes()[0].contentDocument.body);
// }
// Add the tmp div used for rendering with the id `d${id}`
// d+id it will contain a svg with the id "id"
if (cnf.securityLevel === 'sandbox') {
// IF we are in sandboxed mode, we do everyting mermaid related
// in a sandboxed div
const iframe = select('body')
.append('iframe')
.attr('id', 'i' + id)
.attr('style', 'width: 100%; height: 100%;')
.attr('sandbox', '');
// const iframeBody = ;
root = select(iframe.nodes()[0].contentDocument.body);
root.node().style.margin = 0;
} else {
root = select('body');
}
// This is the temporary div
root
.append('div')
.attr('id', 'd' + id)
// this is the seed of the svg to be rendered
.append('svg')
.attr('id', id)
.attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g');
}
txt = encodeEntities(txt);
// Get the tmp element containing the the svg
const element = root.select('#d' + id).node();
const graphType = utils.detectType(txt, cnf);
// insert inline style into svg
const svg = element.firstChild;
const firstChild = svg.firstChild;
let userStyles = '';
// user provided theme CSS
if (cnf.themeCSS !== undefined) {
userStyles += `\n${cnf.themeCSS}`;
}
// user provided theme CSS
if (cnf.fontFamily !== undefined) {
userStyles += `\n:root { --mermaid-font-family: ${cnf.fontFamily}}`;
}
// user provided theme CSS
if (cnf.altFontFamily !== undefined) {
userStyles += `\n:root { --mermaid-alt-font-family: ${cnf.altFontFamily}}`;
}
// classDef
if (graphType === 'flowchart' || graphType === 'flowchart-v2' || graphType === 'graph') {
const classes = flowRenderer.getClasses(txt);
const htmlLabels = cnf.htmlLabels || cnf.flowchart.htmlLabels;
for (const className in classes) {
if (htmlLabels) {
userStyles += `\n.${className} > * { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} span { ${classes[className].styles.join(
' !important; '
)} !important; }`;
} else {
userStyles += `\n.${className} path { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} rect { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} polygon { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} ellipse { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} circle { ${classes[className].styles.join(
' !important; '
)} !important; }`;
if (classes[className].textStyles) {
userStyles += `\n.${className} tspan { ${classes[className].textStyles.join(
' !important; '
)} !important; }`;
}
}
}
}
// log.warn(cnf.themeVariables);
const stylis = (selector, styles) => serialize(compile(`${selector}{${styles}}`), stringify);
const rules = stylis(`#${id}`, getStyles(graphType, userStyles, cnf.themeVariables));
const style1 = document.createElement('style');
style1.innerHTML = `#${id} ` + rules;
svg.insertBefore(style1, firstChild);
// Verify that the generated svgs are ok before removing this
// const style2 = document.createElement('style');
// const cs = window.getComputedStyle(svg);
// style2.innerHTML = `#d${id} * {
// color: ${cs.color};
// // font: ${cs.font};
// // font-family: Arial;
// // font-size: 24px;
// }`;
// svg.insertBefore(style2, firstChild);
try {
switch (graphType) {
case 'git':
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
gitGraphRenderer.setConf(cnf.git);
gitGraphRenderer.draw(txt, id, false);
break;
case 'flowchart':
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowRenderer.setConf(cnf.flowchart);
flowRenderer.draw(txt, id, false);
break;
case 'flowchart-v2':
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
flowRendererV2.setConf(cnf.flowchart);
flowRendererV2.draw(txt, id, false);
break;
case 'sequence':
cnf.sequence.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
if (cnf.sequenceDiagram) {
// backwards compatibility
sequenceRenderer.setConf(Object.assign(cnf.sequence, cnf.sequenceDiagram));
console.error(
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
);
} else {
sequenceRenderer.setConf(cnf.sequence);
}
sequenceRenderer.draw(txt, id);
break;
case 'gantt':
cnf.gantt.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
ganttRenderer.setConf(cnf.gantt);
ganttRenderer.draw(txt, id);
break;
case 'class':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classRenderer.setConf(cnf.class);
classRenderer.draw(txt, id);
break;
case 'classDiagram':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classRendererV2.setConf(cnf.class);
classRendererV2.draw(txt, id);
break;
case 'state':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateRenderer.setConf(cnf.state);
stateRenderer.draw(txt, id);
break;
case 'stateDiagram':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateRendererV2.setConf(cnf.state);
stateRendererV2.draw(txt, id);
break;
case 'info':
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
infoRenderer.setConf(cnf.class);
infoRenderer.draw(txt, id, pkg.version);
break;
case 'pie':
//cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
//pieRenderer.setConf(cnf.pie);
pieRenderer.draw(txt, id, pkg.version);
break;
case 'er':
erRenderer.setConf(cnf.er);
erRenderer.draw(txt, id, pkg.version);
break;
case 'journey':
journeyRenderer.setConf(cnf.journey);
journeyRenderer.draw(txt, id, pkg.version);
break;
case 'requirement':
requirementRenderer.setConf(cnf.requirement);
requirementRenderer.draw(txt, id, pkg.version);
break;
}
} catch (e) {
// errorRenderer.setConf(cnf.class);
errorRenderer.draw(id, pkg.version);
throw e;
}
root
.select(`[id="${id}"]`)
.selectAll('foreignobject > *')
.attr('xmlns', 'http://www.w3.org/1999/xhtml');
// Fix for when the base tag is used
let svgCode = root.select('#d' + id).node().innerHTML;
log.debug('cnf.arrowMarkerAbsolute', cnf.arrowMarkerAbsolute);
if (
(!cnf.arrowMarkerAbsolute || cnf.arrowMarkerAbsolute === 'false') &&
cnf.arrowMarkerAbsolute !== 'sandbox'
) {
svgCode = svgCode.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#', 'g');
}
svgCode = decodeEntities(svgCode);
// Fix for when the br tag is used
svgCode = svgCode.replace(/
/g, '
');
if (cnf.securityLevel === 'sandbox') {
let svgEl = root.select('#d' + id + ' svg').node();
let width = '100%';
let height = '100%';
if (svgEl) {
width = svgEl.viewBox.baseVal.width + 'px';
height = svgEl.viewBox.baseVal.height + 'px';
}
svgCode = `