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
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PlantUML Syntax Fixer and Formatter
|
|
5
|
+
* Automatically fixes common syntax errors in PlantUML code
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PlantUML special characters that need to be escaped or quoted
|
|
10
|
+
*/
|
|
11
|
+
var SPECIAL_CHARS = /[<>{}[\]():;,|&!@#$%^*/+\-=~`'"]/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Common patterns that might need fixing
|
|
15
|
+
*/
|
|
16
|
+
var PATTERNS = {
|
|
17
|
+
// Arrow labels with special characters (not quoted)
|
|
18
|
+
// Matches: A -> B: label with special chars
|
|
19
|
+
// More precise: matches arrow followed by colon and label
|
|
20
|
+
unquotedArrowLabel: /([\w"']+(?:\s*\[[^\]]*\])?)\s*(->|-->|<-|<-|--|\.\.|\.\.>|\.\.\.|\.\.\.>)\s*([\w"']+(?:\s*\[[^\]]*\])?)\s*:\s*([^"\n]+?)(?:\n|$)/g,
|
|
21
|
+
|
|
22
|
+
// Class/method names with special characters
|
|
23
|
+
// Matches: class MyClass<Type>, class MyClass {, etc.
|
|
24
|
+
unquotedClassName: /(class|interface|abstract\s+class|enum|package|namespace)\s+([^{"'\n]+?)(?:\s*\{|\s*extends|\s*implements|\n|$)/g,
|
|
25
|
+
|
|
26
|
+
// Participant names with special characters
|
|
27
|
+
unquotedParticipant: /(participant|actor|boundary|control|entity|database|collections?)\s+([^"'\s]+(?:\s+as|\s*$|\n))/g,
|
|
28
|
+
|
|
29
|
+
// State names with special characters
|
|
30
|
+
unquotedState: /(state|start|end|fork|join)\s+([^{:\n"']+?)(?:\s*\{|:|\n|$)/g,
|
|
31
|
+
|
|
32
|
+
// Note/legend text with special characters
|
|
33
|
+
unquotedNote: /(note\s+(?:left|right|top|bottom|of)\s+[^:]+?:\s*)([^"\n]+)/gi,
|
|
34
|
+
|
|
35
|
+
// Title with special characters
|
|
36
|
+
unquotedTitle: /(title\s+)([^"\n]+)/gi,
|
|
37
|
+
|
|
38
|
+
// Simple arrow label (without colon, just after arrow)
|
|
39
|
+
simpleArrowLabel: /(->|-->|<-|<-|--|\.\.|\.\.>|\.\.\.|\.\.\.>)\s+([^"\n\s][^"\n]*?)(?=\s*(?:->|-->|<-|<-|--|\.\.|@|$|\n))/g
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a string contains special characters that need quoting
|
|
44
|
+
* @param {string} text - Text to check
|
|
45
|
+
* @returns {boolean} - True if contains special characters
|
|
46
|
+
*/
|
|
47
|
+
function needsQuoting (text) {
|
|
48
|
+
if (!text || typeof text !== 'string') {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Trim whitespace
|
|
53
|
+
text = text.trim()
|
|
54
|
+
|
|
55
|
+
// Already quoted
|
|
56
|
+
if ((text.startsWith('"') && text.endsWith('"')) ||
|
|
57
|
+
(text.startsWith("'") && text.endsWith("'"))) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for special characters
|
|
62
|
+
if (SPECIAL_CHARS.test(text)) {
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check for multiple words (spaces) - might need quoting for clarity
|
|
67
|
+
if (/\s+/.test(text) && text.length > 0) {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Quote a string if needed
|
|
76
|
+
* @param {string} text - Text to quote
|
|
77
|
+
* @returns {string} - Quoted text
|
|
78
|
+
*/
|
|
79
|
+
function quoteIfNeeded (text) {
|
|
80
|
+
if (!text || typeof text !== 'string') {
|
|
81
|
+
return text
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
text = text.trim()
|
|
85
|
+
|
|
86
|
+
// Already quoted
|
|
87
|
+
if ((text.startsWith('"') && text.endsWith('"')) ||
|
|
88
|
+
(text.startsWith("'") && text.endsWith("'"))) {
|
|
89
|
+
return text
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Escape internal quotes and wrap
|
|
93
|
+
var escaped = text.replace(/"/g, '\\"')
|
|
94
|
+
return '"' + escaped + '"'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Fix arrow labels that contain special characters
|
|
99
|
+
* @param {string} code - PlantUML code
|
|
100
|
+
* @returns {Object} - {fixed: string, warnings: Array}
|
|
101
|
+
*/
|
|
102
|
+
function fixArrowLabels (code) {
|
|
103
|
+
var warnings = []
|
|
104
|
+
var fixed = code
|
|
105
|
+
|
|
106
|
+
// Fix arrow labels with colon (A -> B: label)
|
|
107
|
+
fixed = fixed.replace(PATTERNS.unquotedArrowLabel, function (match, from, arrow, to, label) {
|
|
108
|
+
label = label.trim()
|
|
109
|
+
|
|
110
|
+
// Skip if already quoted or empty
|
|
111
|
+
if (!label || label.startsWith('"') || label.startsWith("'")) {
|
|
112
|
+
return match
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check if needs quoting
|
|
116
|
+
if (needsQuoting(label)) {
|
|
117
|
+
var quoted = quoteIfNeeded(label)
|
|
118
|
+
warnings.push('Fixed unquoted arrow label: "' + label + '" -> ' + quoted)
|
|
119
|
+
return from + ' ' + arrow + ' ' + to + ': ' + quoted + (match.endsWith('\n') ? '\n' : '')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return match
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Fix simple arrow labels (without colon)
|
|
126
|
+
fixed = fixed.replace(PATTERNS.simpleArrowLabel, function (match, arrow, label) {
|
|
127
|
+
label = label.trim()
|
|
128
|
+
|
|
129
|
+
// Skip if already quoted, empty, or is just whitespace
|
|
130
|
+
if (!label || label.startsWith('"') || label.startsWith("'") || /^\s*$/.test(label)) {
|
|
131
|
+
return match
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Only fix if it contains special characters (not just a simple identifier)
|
|
135
|
+
if (needsQuoting(label)) {
|
|
136
|
+
var quoted = quoteIfNeeded(label)
|
|
137
|
+
warnings.push('Fixed unquoted arrow label: "' + label + '" -> ' + quoted)
|
|
138
|
+
return arrow + ' ' + quoted
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return match
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return { fixed: fixed, warnings: warnings }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Fix class/interface names that contain special characters
|
|
149
|
+
* @param {string} code - PlantUML code
|
|
150
|
+
* @returns {Object} - {fixed: string, warnings: Array}
|
|
151
|
+
*/
|
|
152
|
+
function fixClassNames (code) {
|
|
153
|
+
var warnings = []
|
|
154
|
+
var fixed = code.replace(PATTERNS.unquotedClassName, function (match, keyword, name) {
|
|
155
|
+
name = name.trim()
|
|
156
|
+
|
|
157
|
+
// Skip if already quoted, empty
|
|
158
|
+
if (!name || name.startsWith('"') || name.startsWith("'")) {
|
|
159
|
+
return match
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check if it's a simple identifier without any special chars
|
|
163
|
+
// Simple identifier: letters, numbers, underscore, Unicode (Chinese, etc.), but no special chars
|
|
164
|
+
var isSimpleIdentifier = /^[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*$/.test(name)
|
|
165
|
+
|
|
166
|
+
if (isSimpleIdentifier) {
|
|
167
|
+
return match
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If it contains special characters (including < > for generics), it needs quoting
|
|
171
|
+
if (SPECIAL_CHARS.test(name) || name.includes('<') || name.includes('>')) {
|
|
172
|
+
var quoted = quoteIfNeeded(name)
|
|
173
|
+
warnings.push('Fixed unquoted class/interface name: "' + name + '" -> ' + quoted)
|
|
174
|
+
// Reconstruct the match with quoted name
|
|
175
|
+
var keywordMatch = match.match(new RegExp('^' + keyword.replace(/\s+/g, '\\s+') + '\\s+'))
|
|
176
|
+
var afterKeyword = match.substring(keywordMatch[0].length)
|
|
177
|
+
var rest = afterKeyword.substring(name.length)
|
|
178
|
+
return keywordMatch[0] + quoted + rest
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return match
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
return { fixed: fixed, warnings: warnings }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Fix participant names that contain special characters
|
|
189
|
+
* @param {string} code - PlantUML code
|
|
190
|
+
* @returns {Object} - {fixed: string, warnings: Array}
|
|
191
|
+
*/
|
|
192
|
+
function fixParticipantNames (code) {
|
|
193
|
+
var warnings = []
|
|
194
|
+
var fixed = code.replace(PATTERNS.unquotedParticipant, function (match, keyword, name) {
|
|
195
|
+
// Extract the name part (before "as" or end of line)
|
|
196
|
+
var nameMatch = name.match(/^([^"'\s]+)/)
|
|
197
|
+
if (!nameMatch) {
|
|
198
|
+
return match
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
var actualName = nameMatch[1]
|
|
202
|
+
|
|
203
|
+
// Skip if already quoted or empty
|
|
204
|
+
if (!actualName || actualName.startsWith('"') || actualName.startsWith("'")) {
|
|
205
|
+
return match
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check if needs quoting
|
|
209
|
+
if (needsQuoting(actualName)) {
|
|
210
|
+
var quoted = quoteIfNeeded(actualName)
|
|
211
|
+
warnings.push('Fixed unquoted participant name: "' + actualName + '" -> ' + quoted)
|
|
212
|
+
// Reconstruct: replace the name part
|
|
213
|
+
return match.replace(actualName, quoted)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return match
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return { fixed: fixed, warnings: warnings }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Fix note/legend text that contains special characters
|
|
224
|
+
* @param {string} code - PlantUML code
|
|
225
|
+
* @returns {Object} - {fixed: string, warnings: Array}
|
|
226
|
+
*/
|
|
227
|
+
function fixNoteText (code) {
|
|
228
|
+
var warnings = []
|
|
229
|
+
var fixed = code.replace(PATTERNS.unquotedNote, function (match, prefix, text) {
|
|
230
|
+
text = text.trim()
|
|
231
|
+
|
|
232
|
+
// Skip if already quoted or empty
|
|
233
|
+
if (!text || text.startsWith('"') || text.startsWith("'")) {
|
|
234
|
+
return match
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check if needs quoting
|
|
238
|
+
if (needsQuoting(text)) {
|
|
239
|
+
var quoted = quoteIfNeeded(text)
|
|
240
|
+
warnings.push('Fixed unquoted note text: "' + text + '" -> ' + quoted)
|
|
241
|
+
return prefix + quoted
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return match
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
return { fixed: fixed, warnings: warnings }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Fix title text that contains special characters
|
|
252
|
+
* @param {string} code - PlantUML code
|
|
253
|
+
* @returns {Object} - {fixed: string, warnings: Array}
|
|
254
|
+
*/
|
|
255
|
+
function fixTitleText (code) {
|
|
256
|
+
var warnings = []
|
|
257
|
+
var fixed = code.replace(PATTERNS.unquotedTitle, function (match, prefix, text) {
|
|
258
|
+
text = text.trim()
|
|
259
|
+
|
|
260
|
+
// Skip if already quoted or empty
|
|
261
|
+
if (!text || text.startsWith('"') || text.startsWith("'")) {
|
|
262
|
+
return match
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Title usually should be quoted if it has special chars
|
|
266
|
+
if (needsQuoting(text)) {
|
|
267
|
+
var quoted = quoteIfNeeded(text)
|
|
268
|
+
warnings.push('Fixed unquoted title text: "' + text + '" -> ' + quoted)
|
|
269
|
+
return prefix + quoted
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return match
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
return { fixed: fixed, warnings: warnings }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Fix method/attribute names in class definitions that contain special characters
|
|
280
|
+
* @param {string} code - PlantUML code
|
|
281
|
+
* @returns {Object} - {fixed: string, warnings: Array}
|
|
282
|
+
*/
|
|
283
|
+
function fixMethodNames (code) {
|
|
284
|
+
var warnings = []
|
|
285
|
+
|
|
286
|
+
// Match class definitions with methods/attributes
|
|
287
|
+
// Pattern: visibility name(params): returnType
|
|
288
|
+
var methodPattern = /([+\-~#])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*(?::\s*[^\n{]+)?/g
|
|
289
|
+
var attributePattern = /([+\-~#])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*[^\n{]+/g
|
|
290
|
+
|
|
291
|
+
var fixed = code.replace(methodPattern, function (match, visibility, name) {
|
|
292
|
+
// Method names usually don't need quoting unless they have special chars
|
|
293
|
+
if (SPECIAL_CHARS.test(name)) {
|
|
294
|
+
var quoted = quoteIfNeeded(name)
|
|
295
|
+
warnings.push('Fixed method name with special characters: "' + name + '" -> ' + quoted)
|
|
296
|
+
return match.replace(name, quoted)
|
|
297
|
+
}
|
|
298
|
+
return match
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
fixed = fixed.replace(attributePattern, function (match, visibility, name) {
|
|
302
|
+
// Attribute names usually don't need quoting unless they have special chars
|
|
303
|
+
if (SPECIAL_CHARS.test(name)) {
|
|
304
|
+
var quoted = quoteIfNeeded(name)
|
|
305
|
+
warnings.push('Fixed attribute name with special characters: "' + name + '" -> ' + quoted)
|
|
306
|
+
return match.replace(name, quoted)
|
|
307
|
+
}
|
|
308
|
+
return match
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
return { fixed: fixed, warnings: warnings }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Normalize whitespace and formatting
|
|
316
|
+
* @param {string} code - PlantUML code
|
|
317
|
+
* @returns {Object} - {fixed: string, warnings: Array}
|
|
318
|
+
*/
|
|
319
|
+
function normalizeWhitespace (code) {
|
|
320
|
+
var warnings = []
|
|
321
|
+
var fixed = code
|
|
322
|
+
|
|
323
|
+
// Remove trailing whitespace from lines
|
|
324
|
+
var lines = fixed.split('\n')
|
|
325
|
+
var modified = false
|
|
326
|
+
lines = lines.map(function (line, index) {
|
|
327
|
+
var trimmed = line.replace(/[ \t]+$/, '')
|
|
328
|
+
if (trimmed !== line) {
|
|
329
|
+
modified = true
|
|
330
|
+
}
|
|
331
|
+
return trimmed
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
if (modified) {
|
|
335
|
+
warnings.push('Removed trailing whitespace')
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Normalize multiple consecutive blank lines to maximum 2
|
|
339
|
+
fixed = lines.join('\n')
|
|
340
|
+
var normalized = fixed.replace(/\n{3,}/g, '\n\n')
|
|
341
|
+
if (normalized !== fixed) {
|
|
342
|
+
warnings.push('Normalized excessive blank lines')
|
|
343
|
+
fixed = normalized
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { fixed: fixed, warnings: warnings }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Main function to fix PlantUML syntax
|
|
351
|
+
* @param {string} code - PlantUML source code
|
|
352
|
+
* @param {Object} options - Options object
|
|
353
|
+
* @param {boolean} options.autoFix - Enable auto-fixing (default: false)
|
|
354
|
+
* @param {boolean} options.warnOnFix - Show warnings when fixes are applied (default: true)
|
|
355
|
+
* @returns {string} - Fixed PlantUML code
|
|
356
|
+
*/
|
|
357
|
+
function fixPlantUmlSyntax (code, options) {
|
|
358
|
+
if (!code || typeof code !== 'string') {
|
|
359
|
+
return code
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
options = options || {}
|
|
363
|
+
var autoFix = options.autoFix === true // Default to false, must be explicitly enabled
|
|
364
|
+
var warnOnFix = options.warnOnFix !== false // Default to true
|
|
365
|
+
|
|
366
|
+
if (!autoFix) {
|
|
367
|
+
return code
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
var allWarnings = []
|
|
371
|
+
var fixed = code
|
|
372
|
+
|
|
373
|
+
// Apply all fixes
|
|
374
|
+
var result
|
|
375
|
+
|
|
376
|
+
// Fix arrow labels
|
|
377
|
+
result = fixArrowLabels(fixed)
|
|
378
|
+
fixed = result.fixed
|
|
379
|
+
allWarnings = allWarnings.concat(result.warnings)
|
|
380
|
+
|
|
381
|
+
// Fix class/interface names
|
|
382
|
+
result = fixClassNames(fixed)
|
|
383
|
+
fixed = result.fixed
|
|
384
|
+
allWarnings = allWarnings.concat(result.warnings)
|
|
385
|
+
|
|
386
|
+
// Fix participant names
|
|
387
|
+
result = fixParticipantNames(fixed)
|
|
388
|
+
fixed = result.fixed
|
|
389
|
+
allWarnings = allWarnings.concat(result.warnings)
|
|
390
|
+
|
|
391
|
+
// Fix note text
|
|
392
|
+
result = fixNoteText(fixed)
|
|
393
|
+
fixed = result.fixed
|
|
394
|
+
allWarnings = allWarnings.concat(result.warnings)
|
|
395
|
+
|
|
396
|
+
// Fix title text
|
|
397
|
+
result = fixTitleText(fixed)
|
|
398
|
+
fixed = result.fixed
|
|
399
|
+
allWarnings = allWarnings.concat(result.warnings)
|
|
400
|
+
|
|
401
|
+
// Fix method/attribute names
|
|
402
|
+
result = fixMethodNames(fixed)
|
|
403
|
+
fixed = result.fixed
|
|
404
|
+
allWarnings = allWarnings.concat(result.warnings)
|
|
405
|
+
|
|
406
|
+
// Normalize whitespace (optional, less critical)
|
|
407
|
+
if (options.normalizeWhitespace !== false) {
|
|
408
|
+
result = normalizeWhitespace(fixed)
|
|
409
|
+
fixed = result.fixed
|
|
410
|
+
allWarnings = allWarnings.concat(result.warnings)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Log warnings if any fixes were applied
|
|
414
|
+
if (allWarnings.length > 0 && warnOnFix) {
|
|
415
|
+
console.warn('[PlantUML Syntax Fixer] Applied ' + allWarnings.length + ' fix(es):')
|
|
416
|
+
allWarnings.forEach(function (warning) {
|
|
417
|
+
console.warn(' - ' + warning)
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return fixed
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Check if PlantUML code has syntax errors by attempting to render it
|
|
426
|
+
* @param {string} code - PlantUML source code
|
|
427
|
+
* @param {Function} callback - Callback with (error, hasError, svgOutput)
|
|
428
|
+
* @returns {void}
|
|
429
|
+
*/
|
|
430
|
+
function checkSyntaxError (code, callback) {
|
|
431
|
+
if (!code || typeof code !== 'string') {
|
|
432
|
+
return callback(null, false, null)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
var plantumlExecutor = require('./plantuml-executor')
|
|
436
|
+
var SVG = '-tsvg'
|
|
437
|
+
var PIPE = '-pipe'
|
|
438
|
+
|
|
439
|
+
var child = plantumlExecutor.exec([PIPE, SVG])
|
|
440
|
+
var svgChunks = []
|
|
441
|
+
var errorChunks = []
|
|
442
|
+
|
|
443
|
+
child.stdout.on('data', function (chunk) {
|
|
444
|
+
svgChunks.push(chunk)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
child.stderr.on('data', function (chunk) {
|
|
448
|
+
errorChunks.push(chunk)
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
child.on('error', function (err) {
|
|
452
|
+
callback(err, true, null)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
child.on('close', function (code) {
|
|
456
|
+
var svg = Buffer.concat(svgChunks).toString()
|
|
457
|
+
var errorOutput = Buffer.concat(errorChunks).toString()
|
|
458
|
+
|
|
459
|
+
// Check for error indicators
|
|
460
|
+
// Primary indicators: stderr and exit code (most reliable)
|
|
461
|
+
var hasError = false
|
|
462
|
+
|
|
463
|
+
// Check stderr for errors (most reliable indicator)
|
|
464
|
+
if (errorOutput && errorOutput.length > 0) {
|
|
465
|
+
// PlantUML outputs "Syntax Error? (Assumed diagram type: ...)" to stderr as a warning
|
|
466
|
+
// This is NOT a real error, just PlantUML being uncertain about diagram type
|
|
467
|
+
var isOnlyWarning = /^ERROR\s*\d+\s*Syntax Error\? \(Assumed diagram type:/.test(errorOutput.trim())
|
|
468
|
+
|
|
469
|
+
if (!isOnlyWarning) {
|
|
470
|
+
// Check if stderr contains actual error messages (not just warnings)
|
|
471
|
+
var stderrLower = errorOutput.toLowerCase()
|
|
472
|
+
if (stderrLower.includes('error') ||
|
|
473
|
+
stderrLower.includes('exception') ||
|
|
474
|
+
stderrLower.includes('cannot') ||
|
|
475
|
+
stderrLower.includes('failed')) {
|
|
476
|
+
hasError = true
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// If isOnlyWarning is true, we don't set hasError (it's just a warning, not an error)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Check exit code (reliable indicator)
|
|
483
|
+
// Exit code 200 is used by PlantUML for the "Syntax Error? (Assumed..." warning
|
|
484
|
+
// This is not a real error, so we should ignore it
|
|
485
|
+
if (code !== 0 && code !== 200) {
|
|
486
|
+
hasError = true
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Only check SVG content if stderr/exit code didn't indicate error
|
|
490
|
+
// This avoids false positives from normal text containing keywords
|
|
491
|
+
if (!hasError && svg) {
|
|
492
|
+
// Check if SVG is suspiciously small or empty (might indicate error)
|
|
493
|
+
if (svg.length < 200 && !svg.includes('<svg')) {
|
|
494
|
+
hasError = true
|
|
495
|
+
} else {
|
|
496
|
+
// PlantUML shows "Syntax Error? (Assumed diagram type: ...)" as a warning, not a real error
|
|
497
|
+
// If SVG only contains this warning and nothing else suspicious, it's not a real error
|
|
498
|
+
var hasOnlyWarning = svg.includes('Syntax Error? (Assumed diagram type:')
|
|
499
|
+
|
|
500
|
+
if (!hasOnlyWarning) {
|
|
501
|
+
// Check for actual error messages in SVG (very strict)
|
|
502
|
+
// Only flag if we see clear error patterns that are NOT warnings
|
|
503
|
+
var clearErrorPatterns = [
|
|
504
|
+
/syntax error[^?]/i, // "syntax error" but not "syntax error?"
|
|
505
|
+
/parse error/i,
|
|
506
|
+
/cannot parse/i,
|
|
507
|
+
/unexpected token/i
|
|
508
|
+
]
|
|
509
|
+
|
|
510
|
+
var foundClearError = false
|
|
511
|
+
for (var i = 0; i < clearErrorPatterns.length; i++) {
|
|
512
|
+
if (clearErrorPatterns[i].test(svg)) {
|
|
513
|
+
foundClearError = true
|
|
514
|
+
break
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (foundClearError) {
|
|
519
|
+
hasError = true
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// If hasOnlyWarning is true, we don't set hasError (it's just a warning, not an error)
|
|
523
|
+
}
|
|
524
|
+
} else if (!svg || svg.length === 0) {
|
|
525
|
+
// No SVG output at all is likely an error (only if we don't have stderr/exit code info)
|
|
526
|
+
if (code === 0 && (!errorOutput || errorOutput.length === 0)) {
|
|
527
|
+
// This shouldn't happen, but if it does, it's suspicious
|
|
528
|
+
hasError = true
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
callback(null, hasError, svg)
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
// Write code to stdin
|
|
536
|
+
child.stdin.write(code, 'utf-8')
|
|
537
|
+
child.stdin.end()
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
module.exports = {
|
|
541
|
+
fixPlantUmlSyntax: fixPlantUmlSyntax,
|
|
542
|
+
needsQuoting: needsQuoting,
|
|
543
|
+
quoteIfNeeded: quoteIfNeeded,
|
|
544
|
+
checkSyntaxError: checkSyntaxError
|
|
545
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-plantuml-2",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"plantumlVersion": "1.2026.0",
|
|
5
|
+
"description": "Pure Node.js PlantUML renderer - No Java required! Multiple output formats (PNG, SVG, EPS, ASCII, Unicode) with full UTF-8 support",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"author": "Markus Hedvall <mackanhedvall@gmail.com>",
|
|
8
|
+
"repository": "https://github.com/JaredYe04/node-plantuml-2",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">= 12.x"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"plantuml",
|
|
15
|
+
"uml",
|
|
16
|
+
"diagram",
|
|
17
|
+
"wasm",
|
|
18
|
+
"webassembly",
|
|
19
|
+
"pure-node",
|
|
20
|
+
"no-java",
|
|
21
|
+
"svg",
|
|
22
|
+
"png",
|
|
23
|
+
"markdown",
|
|
24
|
+
"diagram-generator"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"commander": "^2.8.1",
|
|
28
|
+
"node-nailgun-client": "^0.1.0",
|
|
29
|
+
"node-nailgun-server": "^0.1.4",
|
|
30
|
+
"plantuml-encoder": "^1.2.5"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"chai": "^4.x",
|
|
34
|
+
"mocha": "^5.x",
|
|
35
|
+
"standard": "^12.x",
|
|
36
|
+
"shelljs": "^0.8.x"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.js",
|
|
40
|
+
"lib/",
|
|
41
|
+
"vendor/plantuml.jar",
|
|
42
|
+
"vendor/wasm/plantuml.wasm",
|
|
43
|
+
"resources/",
|
|
44
|
+
"nail/plantumlnail.jar",
|
|
45
|
+
"scripts/download.js",
|
|
46
|
+
"scripts/get-vizjs.js"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"prepublish": "node scripts/get-plantuml-jar.js --latest || echo 'JAR download failed, continuing...'",
|
|
50
|
+
"build:wasm:publish": "node scripts/get-plantuml-jar.js --latest && node scripts/build-plantuml-wasm.js",
|
|
51
|
+
"postinstall": "node scripts/get-vizjs.js",
|
|
52
|
+
"test": "standard && node test/fixtures/prepare.js && mocha",
|
|
53
|
+
"test:batch": "node test/batch-convert-test.js",
|
|
54
|
+
"test:batch:svg": "node test/batch-convert-test.js --svg",
|
|
55
|
+
"test:batch:png": "node test/batch-convert-test.js --png",
|
|
56
|
+
"build": "node nail/build.js",
|
|
57
|
+
"build:wasm": "node scripts/build-plantuml-wasm.js",
|
|
58
|
+
"build:wasm:bytecoder": "node scripts/build-plantuml-wasm.js",
|
|
59
|
+
"build:all": "node scripts/build-all.js",
|
|
60
|
+
"build:all:jar-only": "node scripts/build-all.js --skip-wasm",
|
|
61
|
+
"build:all:wasm-only": "node scripts/build-all.js --skip-jar"
|
|
62
|
+
},
|
|
63
|
+
"bin": {
|
|
64
|
+
"puml": "index.js"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'General styling
|
|
2
|
+
skinparam shadowing false
|
|
3
|
+
hide circle
|
|
4
|
+
|
|
5
|
+
skinparam noteBackgroundColor White
|
|
6
|
+
skinparam noteBorderColor Black
|
|
7
|
+
|
|
8
|
+
'Class diagram styling
|
|
9
|
+
skinparam packageBackgroundColor White
|
|
10
|
+
skinparam packageBorderColor Black
|
|
11
|
+
skinparam classAttributeIconSize 0
|
|
12
|
+
skinparam class {
|
|
13
|
+
BackgroundColor White
|
|
14
|
+
ArrowColor Black
|
|
15
|
+
BorderColor Black
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
'Sequence diagram styling
|
|
19
|
+
skinparam sequence {
|
|
20
|
+
ArrowColor Black
|
|
21
|
+
ParticipantBorderColor Black
|
|
22
|
+
ParticipantBackgroundColor White
|
|
23
|
+
LifeLineBorderColor Black
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
'Use case diagram styling
|
|
27
|
+
skinparam usecase {
|
|
28
|
+
BackgroundColor White
|
|
29
|
+
ArrowColor Black
|
|
30
|
+
BorderColor Black
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
skinparam monochrome true
|