forked from noppa/xmllint-wasm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
143 lines (125 loc) · 3.41 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
const assert = require('assert').strict;
const { Worker } = require('worker_threads');
const workerModule = require.resolve('./xmllint_worker.js');
function normalizeInput(fileInput, extension) {
if (!Array.isArray(fileInput)) fileInput = [fileInput];
return fileInput.map((xmlInfo, i) => {
if (typeof xmlInfo === 'string') {
return {
fileName: `file_${i}.${extension}`,
contents: xmlInfo,
};
} else {
return xmlInfo;
}
});
}
function preprocessOptions(options) {
const xmls = normalizeInput(options.xml, 'xml');
const extension = options.extension || 'schema';
assert(['schema', 'relaxng'].includes(extension));
const schemas = normalizeInput(options.schema, 'xsd');
const preloads = normalizeInput(options.preload || [], 'xml');
const normalization = options.normalization || '';
assert(['', 'format', 'c14n'].includes(normalization));
const inputFiles = xmls.concat(schemas, preloads);
const args = [];
schemas.forEach(function(schema) {
args.push(`--${extension}`);
args.push(schema['fileName']);
});
if (normalization) {
args.push(`--${normalization}`);
};
xmls.forEach(function(xml) {
args.push(xml['fileName']);
});
return { inputFiles, args };
}
function validationSucceeded(exitCode) {
if (exitCode === 0) {
return true;
} else if (exitCode === 3 || exitCode === 4 /* validationError */) {
return false;
} else /* unknown situation */ {
return null;
}
}
function parseErrors(/** @type {string} */output) {
const errorLines = output
.split('\n')
.slice(0, -2);
return errorLines.map(line => {
const [fileName, lineNumber, ...rest] = line.split(':');
if (fileName && lineNumber && rest.length) {
return {
rawMessage: line,
message: rest.join(':').trim(),
loc: {
fileName,
lineNumber: parseInt(lineNumber),
}
};
} else {
return {
rawMessage: line,
message: line,
loc: null,
};
}
}).filter(errorInfo => {
// xmllint outputs "file.xml validates" for those files that are valid.
const wasValid = !errorInfo.loc && errorInfo.rawMessage
.trim()
.endsWith(' validates');
// don't list those files in errors list
return !wasValid;
});
}
function validateXML(options) {
preprocessedOptions = preprocessOptions(options);
return new Promise(function validateXMLPromiseCb(resolve, reject) {
const worker = new Worker(workerModule, {
workerData: preprocessedOptions
});
let stdout = '';
let stderr = '';
function onMessage({isStdout, txt}) {
var s = String.fromCharCode(txt)
if (isStdout) {
stdout += s;
} else {
stderr += s;
}
}
function onExit(exitCode) {
const valid = validationSucceeded(exitCode);
if (valid === null) {
const err = new Error(stderr);
err.code = exitCode;
reject(err);
} else {
resolve({
valid: valid,
normalized: stdout,
errors: valid ? [] : parseErrors(stderr),
rawOutput: stderr
/* Traditionally, stdout has been suppressed both
* by libxml2 compile options as well as explict
* --noout in arguments; hence »rawOutput« refers
* only to stderr, which is a reasonable attribute value
* despite the slightly odd attribute name.
*/
});
}
}
function onError(err) {
console.error('Unexpected error event from worker: ' + err);
reject(err);
}
worker.on('message', onMessage);
worker.on('exit', onExit);
worker.on('error', onError);
});
}
module.exports.validateXML = validateXML;