node-plantuml-2 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1053 -0
- package/index.js +8 -0
- package/lib/node-plantuml-cmd.js +110 -0
- package/lib/node-plantuml.js +446 -0
- package/lib/plantuml-executor-wasm.js +295 -0
- package/lib/plantuml-executor.js +165 -0
- package/lib/plantuml-syntax-fixer.js +545 -0
- package/nail/plantumlnail.jar +0 -0
- package/package.json +66 -0
- package/resources/classic.puml +31 -0
- package/resources/monochrome.puml +1 -0
- package/scripts/download.js +95 -0
- package/scripts/get-vizjs.js +51 -0
- package/vendor/plantuml.jar +0 -0
package/index.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var fs = require('fs')
|
|
4
|
+
var os = require('os')
|
|
5
|
+
var commander = require('commander')
|
|
6
|
+
var plantuml = require('./node-plantuml')
|
|
7
|
+
var pack = require('../package.json')
|
|
8
|
+
|
|
9
|
+
function getFormatFromOptions (options) {
|
|
10
|
+
if (options.unicode) {
|
|
11
|
+
return 'unicode'
|
|
12
|
+
} else if (options.ascii) {
|
|
13
|
+
return 'ascii'
|
|
14
|
+
} else if (options.svg) {
|
|
15
|
+
return 'svg'
|
|
16
|
+
} else if (options.eps) {
|
|
17
|
+
return 'eps'
|
|
18
|
+
}
|
|
19
|
+
return 'png'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function generate (file, options) {
|
|
23
|
+
options.format = getFormatFromOptions(options)
|
|
24
|
+
var gen
|
|
25
|
+
if (file) {
|
|
26
|
+
gen = plantuml.generate(file, options)
|
|
27
|
+
} else if (options.text) {
|
|
28
|
+
options.text = '@startuml' + os.EOL + options.text + os.EOL + '@enduml'
|
|
29
|
+
gen = plantuml.generate(options.text, options)
|
|
30
|
+
} else {
|
|
31
|
+
gen = plantuml.generate(options)
|
|
32
|
+
process.stdin.pipe(gen.in)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (options.output) {
|
|
36
|
+
var fileStream = fs.createWriteStream(options.output)
|
|
37
|
+
gen.out.pipe(fileStream)
|
|
38
|
+
} else {
|
|
39
|
+
gen.out.pipe(process.stdout)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function encode (file, options) {
|
|
44
|
+
var en
|
|
45
|
+
if (file) {
|
|
46
|
+
en = plantuml.encode(file)
|
|
47
|
+
} else if (options.text) {
|
|
48
|
+
en = plantuml.encode(options.text)
|
|
49
|
+
} else {
|
|
50
|
+
en = plantuml.encode()
|
|
51
|
+
process.stdin.pipe(en.in)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (en) {
|
|
55
|
+
en.out.on('data', function (chunk) { process.stdout.write(chunk) })
|
|
56
|
+
en.out.on('end', function () {
|
|
57
|
+
process.stdout.write('\n')
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function decode (url, options) {
|
|
63
|
+
var de = plantuml.decode(url)
|
|
64
|
+
de.out.pipe(process.stdout)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function testdot () {
|
|
68
|
+
var dot = plantuml.testdot()
|
|
69
|
+
dot.out.pipe(process.stdout)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
commander.version(pack.version)
|
|
73
|
+
|
|
74
|
+
commander
|
|
75
|
+
.command('generate [file]')
|
|
76
|
+
.description('Generate an UML diagram from PlantUML source')
|
|
77
|
+
.option('-p, --png', 'ouput an UML diagram as a PNG image')
|
|
78
|
+
.option('-s, --svg', 'ouput an UML diagram as an SVG image')
|
|
79
|
+
.option('-e, --eps', 'ouput an UML diagram as an EPS image')
|
|
80
|
+
.option('-u, --unicode', 'ouput an UML diagram in unicode text')
|
|
81
|
+
.option('-a, --ascii', 'ouput an UML diagram in ASCII text')
|
|
82
|
+
.option('-o --output [file]', 'the file in which to save the diagram')
|
|
83
|
+
.option('-c, --config [file]', 'config file read before the diagram')
|
|
84
|
+
.option('-t, --text [text]', 'UML text to generate from')
|
|
85
|
+
.option('-d, --dot [file]', 'specify Graphviz dot executable')
|
|
86
|
+
.option('-i, --include [path]', 'specify the path to include from')
|
|
87
|
+
.option('-C, --charset [charset]', 'specify the charset of PlantUML source')
|
|
88
|
+
.action(generate)
|
|
89
|
+
|
|
90
|
+
commander
|
|
91
|
+
.command('encode [file]')
|
|
92
|
+
.description('Encodes PlantUML source')
|
|
93
|
+
.option('-t, --text [text]', 'UML text to encode')
|
|
94
|
+
.action(encode)
|
|
95
|
+
|
|
96
|
+
commander
|
|
97
|
+
.command('decode <url>')
|
|
98
|
+
.description('Decodes PlantUML source')
|
|
99
|
+
.action(decode)
|
|
100
|
+
|
|
101
|
+
commander
|
|
102
|
+
.command('testdot')
|
|
103
|
+
.description('Test the installation of Graphviz dot')
|
|
104
|
+
.action(testdot)
|
|
105
|
+
|
|
106
|
+
commander.parse(process.argv)
|
|
107
|
+
|
|
108
|
+
if (!process.argv.slice(2).length || process.argv[2] === 'help') {
|
|
109
|
+
commander.outputHelp()
|
|
110
|
+
}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var plantumlExecutor = require('./plantuml-executor')
|
|
4
|
+
var fs = require('fs')
|
|
5
|
+
var stream = require('stream')
|
|
6
|
+
var util = require('util')
|
|
7
|
+
var path = require('path')
|
|
8
|
+
var plantumlEncoder = require('plantuml-encoder')
|
|
9
|
+
var syntaxFixer = require('./plantuml-syntax-fixer')
|
|
10
|
+
|
|
11
|
+
var DECODE = '-decodeurl'
|
|
12
|
+
var PIPE = '-pipe'
|
|
13
|
+
var UNICODE = '-tutxt'
|
|
14
|
+
var ASCII = '-ttxt'
|
|
15
|
+
var SVG = '-tsvg'
|
|
16
|
+
var EPS = '-eps'
|
|
17
|
+
var CONFIG = '-config'
|
|
18
|
+
var TESTDOT = '-testdot'
|
|
19
|
+
var DOT = '-graphvizdot'
|
|
20
|
+
var CHARSET = '-charset'
|
|
21
|
+
|
|
22
|
+
var CONFIGS = {
|
|
23
|
+
classic: path.join(__dirname, '../resources/classic.puml'),
|
|
24
|
+
monochrome: path.join(__dirname, '../resources/monochrome.puml')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports.useNailgun = plantumlExecutor.useNailgun
|
|
28
|
+
|
|
29
|
+
function PlantumlEncodeStream () {
|
|
30
|
+
stream.Transform.call(this)
|
|
31
|
+
this.chunks = []
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
util.inherits(PlantumlEncodeStream, stream.Transform)
|
|
35
|
+
|
|
36
|
+
PlantumlEncodeStream.prototype._transform = function (chunk, encoding, done) {
|
|
37
|
+
this.chunks.push(chunk)
|
|
38
|
+
done()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
PlantumlEncodeStream.prototype._flush = function (done) {
|
|
42
|
+
var uml = Buffer.concat(this.chunks).toString()
|
|
43
|
+
var encoded = plantumlEncoder.encode(uml)
|
|
44
|
+
this.push(Buffer.from(encoded, 'ascii'))
|
|
45
|
+
done()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isPath (input) {
|
|
49
|
+
try {
|
|
50
|
+
fs.lstatSync(input)
|
|
51
|
+
return true
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Detect if text contains characters that need special font support
|
|
59
|
+
* (Chinese, Japanese, Korean, etc.)
|
|
60
|
+
*/
|
|
61
|
+
function needsFontSupport (text) {
|
|
62
|
+
if (!text || typeof text !== 'string') {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
// Check for CJK characters (Chinese, Japanese Hiragana/Katakana, Korean)
|
|
66
|
+
return /[\u4e00-\u9fa5\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(text)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Detect if text contains Korean characters
|
|
71
|
+
*/
|
|
72
|
+
function hasKorean (text) {
|
|
73
|
+
if (!text || typeof text !== 'string') {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
// Korean Hangul Syllables: \uac00-\ud7af
|
|
77
|
+
return /[\uac00-\ud7af]/.test(text)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get default font name based on platform and detected languages
|
|
82
|
+
* @param {string} code - PlantUML source code to detect languages from
|
|
83
|
+
*/
|
|
84
|
+
function getDefaultFont (code) {
|
|
85
|
+
var platform = process.platform
|
|
86
|
+
var hasKR = hasKorean(code || '')
|
|
87
|
+
|
|
88
|
+
if (platform === 'win32') {
|
|
89
|
+
// Windows: Use Malgun Gothic (맑은 고딕) for Korean, Microsoft YaHei for CJ
|
|
90
|
+
if (hasKR) {
|
|
91
|
+
return 'Malgun Gothic'
|
|
92
|
+
}
|
|
93
|
+
return 'Microsoft YaHei'
|
|
94
|
+
} else if (platform === 'darwin') {
|
|
95
|
+
// macOS: Use AppleGothic for Korean, PingFang SC for CJ
|
|
96
|
+
if (hasKR) {
|
|
97
|
+
return 'AppleGothic'
|
|
98
|
+
}
|
|
99
|
+
return 'PingFang SC'
|
|
100
|
+
} else {
|
|
101
|
+
// Linux: Use Noto Sans CJK (supports all CJK languages)
|
|
102
|
+
return 'Noto Sans CJK SC'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Add font configuration to PlantUML code if needed
|
|
108
|
+
* @param {string} code - PlantUML source code
|
|
109
|
+
* @param {Object} options - Options object (may contain fontName)
|
|
110
|
+
* @returns {string} - Modified code with font config if needed
|
|
111
|
+
*/
|
|
112
|
+
function addFontConfigIfNeeded (code, options) {
|
|
113
|
+
if (!code || typeof code !== 'string') {
|
|
114
|
+
return code
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If user explicitly specified a font, don't override
|
|
118
|
+
if (options && options.fontName) {
|
|
119
|
+
return code
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if font config already exists
|
|
123
|
+
if (code.includes('defaultFontName') || code.includes('skinparam defaultFontName')) {
|
|
124
|
+
return code
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if code needs font support
|
|
128
|
+
if (!needsFontSupport(code)) {
|
|
129
|
+
return code
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get default font for platform, considering detected languages in code
|
|
133
|
+
var fontName = getDefaultFont(code)
|
|
134
|
+
var fontSize = options && options.fontSize ? options.fontSize : 12
|
|
135
|
+
|
|
136
|
+
// Add font configuration
|
|
137
|
+
// Priority: after !theme (if exists), otherwise after @startuml/@startgantt/@startmindmap
|
|
138
|
+
var fontConfig = 'skinparam defaultFontName "' + fontName + '"\n'
|
|
139
|
+
|
|
140
|
+
// Check if fontSize is already set, if not add it
|
|
141
|
+
if (!code.includes('defaultFontSize') && !code.includes('skinparam defaultFontSize')) {
|
|
142
|
+
fontConfig += 'skinparam defaultFontSize ' + fontSize + '\n'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Try to insert after !theme first (theme may override fonts, so we add after it)
|
|
146
|
+
var themePattern = /(!theme\s+[^\n]+\n)/i
|
|
147
|
+
if (themePattern.test(code)) {
|
|
148
|
+
return code.replace(themePattern, '$1' + fontConfig)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Otherwise, insert after @startuml, @startgantt, or @startmindmap
|
|
152
|
+
var patterns = [
|
|
153
|
+
/(@startuml\s*\n)/i,
|
|
154
|
+
/(@startgantt\s*\n)/i,
|
|
155
|
+
/(@startmindmap\s*\n)/i
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
for (var i = 0; i < patterns.length; i++) {
|
|
159
|
+
if (patterns[i].test(code)) {
|
|
160
|
+
return code.replace(patterns[i], '$1' + fontConfig)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// If no start directive found, prepend to the beginning
|
|
165
|
+
return fontConfig + code
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function arrangeArguments (input, options, callback) {
|
|
169
|
+
if (typeof input === 'function') {
|
|
170
|
+
callback = input
|
|
171
|
+
input = undefined
|
|
172
|
+
} else {
|
|
173
|
+
if (typeof options === 'function') {
|
|
174
|
+
callback = options
|
|
175
|
+
options = undefined
|
|
176
|
+
}
|
|
177
|
+
if (typeof input !== 'string' && !(input instanceof String)) {
|
|
178
|
+
options = input
|
|
179
|
+
input = undefined
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
input: input,
|
|
185
|
+
options: options,
|
|
186
|
+
callback: callback
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function joinOptions (argv, options) {
|
|
191
|
+
options.format = options.format || 'png'
|
|
192
|
+
switch (options.format) {
|
|
193
|
+
case 'ascii':
|
|
194
|
+
argv.push(ASCII)
|
|
195
|
+
break
|
|
196
|
+
case 'unicode':
|
|
197
|
+
argv.push(UNICODE)
|
|
198
|
+
break
|
|
199
|
+
case 'svg':
|
|
200
|
+
argv.push(SVG)
|
|
201
|
+
break
|
|
202
|
+
case 'eps':
|
|
203
|
+
argv.push(EPS)
|
|
204
|
+
break
|
|
205
|
+
case 'png':
|
|
206
|
+
default:
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (options.config) {
|
|
211
|
+
var template = CONFIGS[options.config]
|
|
212
|
+
var file = template || options.config
|
|
213
|
+
argv.push(CONFIG)
|
|
214
|
+
argv.push(file)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (options.dot) {
|
|
218
|
+
argv.push(DOT)
|
|
219
|
+
argv.push(options.dot)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (options.charset) {
|
|
223
|
+
argv.push(CHARSET)
|
|
224
|
+
argv.push(options.charset)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return argv
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// function generateFromStdin (child) {
|
|
231
|
+
// return {
|
|
232
|
+
// in: child.stdin,
|
|
233
|
+
// out: child.stdout
|
|
234
|
+
// }
|
|
235
|
+
// }
|
|
236
|
+
|
|
237
|
+
function generateFromFile (filePath, child, options) {
|
|
238
|
+
// Read file content, fix syntax if enabled, add font config if needed, then write to stdin
|
|
239
|
+
var code = fs.readFileSync(filePath, 'utf-8')
|
|
240
|
+
code = syntaxFixer.fixPlantUmlSyntax(code, options)
|
|
241
|
+
code = addFontConfigIfNeeded(code, options)
|
|
242
|
+
child.stdin.write(code, 'utf-8')
|
|
243
|
+
child.stdin.end()
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
out: child.stdout
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function generateFromText (text, child, options) {
|
|
251
|
+
text = syntaxFixer.fixPlantUmlSyntax(text, options)
|
|
252
|
+
text = addFontConfigIfNeeded(text, options)
|
|
253
|
+
child.stdin.write(text)
|
|
254
|
+
child.stdin.end()
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
out: child.stdout
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports.generate = function (input, options, callback) {
|
|
262
|
+
var args = arrangeArguments(input, options, callback)
|
|
263
|
+
input = args.input
|
|
264
|
+
options = args.options || {}
|
|
265
|
+
callback = args.callback
|
|
266
|
+
|
|
267
|
+
var o = joinOptions([PIPE], options)
|
|
268
|
+
var child = plantumlExecutor.exec(o, options.include, callback)
|
|
269
|
+
|
|
270
|
+
if (!input) {
|
|
271
|
+
// For stdin, we need to handle font config in a transform stream
|
|
272
|
+
var Transform = stream.Transform
|
|
273
|
+
var fontTransform = new Transform({
|
|
274
|
+
transform: function (chunk, encoding, done) {
|
|
275
|
+
// Collect all chunks first
|
|
276
|
+
if (!this._chunks) {
|
|
277
|
+
this._chunks = []
|
|
278
|
+
}
|
|
279
|
+
this._chunks.push(chunk)
|
|
280
|
+
done()
|
|
281
|
+
},
|
|
282
|
+
flush: function (done) {
|
|
283
|
+
var code = Buffer.concat(this._chunks).toString('utf-8')
|
|
284
|
+
code = syntaxFixer.fixPlantUmlSyntax(code, options)
|
|
285
|
+
code = addFontConfigIfNeeded(code, options)
|
|
286
|
+
this.push(Buffer.from(code, 'utf-8'))
|
|
287
|
+
done()
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
fontTransform.pipe(child.stdin)
|
|
291
|
+
return {
|
|
292
|
+
in: fontTransform,
|
|
293
|
+
out: child.stdout
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
if (isPath(input)) {
|
|
297
|
+
return generateFromFile(input, child, options)
|
|
298
|
+
} else {
|
|
299
|
+
return generateFromText(input, child, options)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function encodeFromStdin (encodeStream) {
|
|
305
|
+
return {
|
|
306
|
+
in: encodeStream,
|
|
307
|
+
out: encodeStream
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function encodeFromFile (path, encodeStream) {
|
|
312
|
+
var rs = fs.createReadStream(path)
|
|
313
|
+
rs.pipe(encodeStream)
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
out: encodeStream
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function encodeFromText (text, encodeStream) {
|
|
321
|
+
encodeStream.write(text)
|
|
322
|
+
encodeStream.end()
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
out: encodeStream
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports.encode = function (input, options, callback) {
|
|
330
|
+
var args = arrangeArguments(input, options, callback)
|
|
331
|
+
input = args.input
|
|
332
|
+
options = args.options || {}
|
|
333
|
+
callback = args.callback
|
|
334
|
+
|
|
335
|
+
var encodeStream = new PlantumlEncodeStream()
|
|
336
|
+
|
|
337
|
+
if (typeof callback === 'function') {
|
|
338
|
+
var chunks = []
|
|
339
|
+
encodeStream.on('data', function (chunk) { chunks.push(chunk) })
|
|
340
|
+
encodeStream.on('end', function () {
|
|
341
|
+
var data = Buffer.concat(chunks)
|
|
342
|
+
callback(null, data.toString())
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!input) {
|
|
347
|
+
return encodeFromStdin(encodeStream)
|
|
348
|
+
} else {
|
|
349
|
+
if (isPath(input)) {
|
|
350
|
+
return encodeFromFile(input, encodeStream)
|
|
351
|
+
} else {
|
|
352
|
+
return encodeFromText(input, encodeStream)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
module.exports.decode = function (encoded, callback) {
|
|
358
|
+
var child = plantumlExecutor.exec([DECODE, encoded], callback)
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
out: child.stdout
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports.testdot = function (callback) {
|
|
366
|
+
var child = plantumlExecutor.exec([TESTDOT])
|
|
367
|
+
|
|
368
|
+
var chunks = []
|
|
369
|
+
child.stdout.on('data', function (chunk) { chunks.push(chunk) })
|
|
370
|
+
child.stdout.on('end', function () {
|
|
371
|
+
var data = Buffer.concat(chunks)
|
|
372
|
+
var dotOkCheck = 'Installation seems OK. File generation OK'
|
|
373
|
+
var dotOk = data.toString().indexOf(dotOkCheck) !== -1
|
|
374
|
+
if (typeof callback === 'function') callback(dotOk)
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
out: child.stdout
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Standalone syntax fixing service
|
|
384
|
+
* Checks if code has syntax errors, and if so, attempts to fix them
|
|
385
|
+
* @param {string} code - PlantUML source code
|
|
386
|
+
* @param {Object} options - Options object
|
|
387
|
+
* @param {Function} callback - Callback with (error, fixedCode, wasFixed)
|
|
388
|
+
* @returns {void}
|
|
389
|
+
*/
|
|
390
|
+
module.exports.fixSyntax = function (code, options, callback) {
|
|
391
|
+
if (typeof options === 'function') {
|
|
392
|
+
callback = options
|
|
393
|
+
options = {}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
options = options || {}
|
|
397
|
+
|
|
398
|
+
if (!code || typeof code !== 'string') {
|
|
399
|
+
if (typeof callback === 'function') {
|
|
400
|
+
return callback(null, code, false)
|
|
401
|
+
}
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// First, check if there's a syntax error
|
|
406
|
+
syntaxFixer.checkSyntaxError(code, function (err, hasError, svgOutput) {
|
|
407
|
+
if (err) {
|
|
408
|
+
// If check failed, assume there's an error and try to fix
|
|
409
|
+
hasError = true
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// If no error detected, return original code unchanged
|
|
413
|
+
if (!hasError) {
|
|
414
|
+
if (typeof callback === 'function') {
|
|
415
|
+
return callback(null, code, false)
|
|
416
|
+
}
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// There's an error, try to fix it
|
|
421
|
+
var fixOptions = {
|
|
422
|
+
autoFix: true,
|
|
423
|
+
warnOnFix: options.warnOnFix !== false,
|
|
424
|
+
normalizeWhitespace: options.normalizeWhitespace !== false
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
var fixedCode = syntaxFixer.fixPlantUmlSyntax(code, fixOptions)
|
|
428
|
+
|
|
429
|
+
// Check again if the fixed code works
|
|
430
|
+
syntaxFixer.checkSyntaxError(fixedCode, function (err2, stillHasError, svgOutput2) {
|
|
431
|
+
if (err2) {
|
|
432
|
+
// If second check failed, still return the fixed code
|
|
433
|
+
if (typeof callback === 'function') {
|
|
434
|
+
return callback(null, fixedCode, true)
|
|
435
|
+
}
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// If fixed code still has errors, return it anyway (we tried our best)
|
|
440
|
+
// If fixed code works, return it
|
|
441
|
+
if (typeof callback === 'function') {
|
|
442
|
+
return callback(null, fixedCode, true)
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
})
|
|
446
|
+
}
|