0
# XML Generation
1
2
XML generation utilities in Istanbul Lib Report provide well-formed, indented XML output for creating structured reports. The XMLWriter class wraps content writers and manages proper tag opening, closing, and nesting with automatic indentation.
3
4
## Capabilities
5
6
### XMLWriter Class
7
8
The main XML writing utility that produces well-formed, indented XML with proper tag management and nesting validation.
9
10
```javascript { .api }
11
/**
12
* A utility class to produce well-formed, indented XML
13
*/
14
class XMLWriter {
15
/**
16
* Create an XML writer wrapping a content writer
17
* @param {ContentWriter} contentWriter - The content writer that this utility wraps
18
*/
19
constructor(contentWriter);
20
21
/**
22
* Writes the opening XML tag with the supplied attributes
23
* @param {string} name - Tag name
24
* @param {Object} [attrs] - Attributes for the tag as key-value pairs
25
*/
26
openTag(name, attrs);
27
28
/**
29
* Closes an open XML tag
30
* @param {string} name - Tag name to close (must match currently open tag)
31
* @throws {Error} If tag name doesn't match the currently open tag
32
* @throws {Error} If attempting to close tag when none are open
33
*/
34
closeTag(name);
35
36
/**
37
* Writes a tag and its value, opening and closing it at the same time
38
* @param {string} name - Tag name
39
* @param {Object} [attrs] - Tag attributes as key-value pairs
40
* @param {string} [content] - Optional tag content
41
*/
42
inlineTag(name, attrs, content);
43
44
/**
45
* Closes all open tags and ends the document
46
*/
47
closeAll();
48
}
49
```
50
51
**Usage Examples:**
52
53
```javascript
54
const libReport = require('istanbul-lib-report');
55
56
// Create context and get XML writer
57
const context = libReport.createContext({ dir: 'reports', coverageMap });
58
const contentWriter = context.getWriter().writeFile('coverage.xml');
59
const xmlWriter = context.getXMLWriter(contentWriter);
60
61
// Basic XML structure
62
xmlWriter.openTag('coverage', { version: '1.0', timestamp: Date.now() });
63
xmlWriter.openTag('project', { name: 'my-project' });
64
xmlWriter.inlineTag('metric', { type: 'statements', covered: '85', total: '100' });
65
xmlWriter.inlineTag('metric', { type: 'branches', covered: '78', total: '90' });
66
xmlWriter.closeTag('project');
67
xmlWriter.closeTag('coverage');
68
69
contentWriter.close();
70
```
71
72
### Opening Tags
73
74
Creates opening XML tags with optional attributes and manages the tag stack for proper nesting.
75
76
```javascript { .api }
77
/**
78
* Opens an XML tag with optional attributes
79
* @param {string} name - The tag name
80
* @param {Object} [attrs] - Attributes as key-value pairs
81
*/
82
openTag(name, attrs);
83
```
84
85
**Usage Example:**
86
87
```javascript
88
// Simple tag
89
xmlWriter.openTag('report');
90
91
// Tag with attributes
92
xmlWriter.openTag('file', {
93
path: '/src/utils.js',
94
covered: '12',
95
total: '15'
96
});
97
98
// Tag with multiple attributes
99
xmlWriter.openTag('coverage', {
100
'line-rate': '0.85',
101
'branch-rate': '0.78',
102
'lines-covered': '850',
103
'lines-valid': '1000',
104
complexity: '0',
105
version: '1.9',
106
timestamp: '1640995200'
107
});
108
```
109
110
### Closing Tags
111
112
Closes XML tags with validation to ensure proper nesting and tag matching.
113
114
```javascript { .api }
115
/**
116
* Closes an open XML tag with validation
117
* @param {string} name - Tag name to close
118
* @throws {Error} If tag doesn't match currently open tag
119
*/
120
closeTag(name);
121
```
122
123
**Usage Example:**
124
125
```javascript
126
xmlWriter.openTag('methods');
127
xmlWriter.openTag('method', { name: 'calculateTotal', signature: '()V' });
128
xmlWriter.closeTag('method');
129
xmlWriter.closeTag('methods');
130
131
// Error handling
132
try {
133
xmlWriter.openTag('coverage');
134
xmlWriter.closeTag('report'); // Error: mismatched tag
135
} catch (error) {
136
console.error('XML tag mismatch:', error.message);
137
}
138
```
139
140
### Inline Tags
141
142
Creates self-contained tags that open and close in a single operation, optionally with content.
143
144
```javascript { .api }
145
/**
146
* Creates a complete tag with optional content
147
* @param {string} name - Tag name
148
* @param {Object} [attrs] - Tag attributes
149
* @param {string} [content] - Optional tag content
150
*/
151
inlineTag(name, attrs, content);
152
```
153
154
**Usage Examples:**
155
156
```javascript
157
// Self-closing tag
158
xmlWriter.inlineTag('metric', { type: 'statements', value: '85' });
159
// Produces: <metric type="statements" value="85"/>
160
161
// Tag with content
162
xmlWriter.inlineTag('name', null, 'istanbul-lib-report');
163
// Produces: <name>istanbul-lib-report</name>
164
165
// Tag with attributes and content
166
xmlWriter.inlineTag('description', { lang: 'en' }, 'Coverage reporting library');
167
// Produces: <description lang="en">Coverage reporting library</description>
168
169
// Multiple inline tags for metrics
170
const metrics = {
171
statements: { covered: 850, total: 1000 },
172
branches: { covered: 780, total: 900 },
173
functions: { covered: 95, total: 100 }
174
};
175
176
Object.entries(metrics).forEach(([type, data]) => {
177
xmlWriter.inlineTag('metric', {
178
type,
179
covered: data.covered.toString(),
180
total: data.total.toString(),
181
rate: (data.covered / data.total).toFixed(2)
182
});
183
});
184
```
185
186
### Closing All Tags
187
188
Utility method to close all open tags in the correct order, useful for cleanup and error recovery.
189
190
```javascript { .api }
191
/**
192
* Closes all open tags in reverse order
193
*/
194
closeAll();
195
```
196
197
**Usage Example:**
198
199
```javascript
200
// Safe cleanup pattern
201
try {
202
xmlWriter.openTag('coverage');
203
xmlWriter.openTag('project');
204
xmlWriter.openTag('packages');
205
// ... generate report content
206
207
// Manual closing (normal flow)
208
xmlWriter.closeTag('packages');
209
xmlWriter.closeTag('project');
210
xmlWriter.closeTag('coverage');
211
} catch (error) {
212
// Error recovery - close all remaining tags
213
xmlWriter.closeAll();
214
throw error;
215
} finally {
216
contentWriter.close();
217
}
218
```
219
220
### Complete XML Report Example
221
222
Here's a comprehensive example showing how to generate a complete XML coverage report:
223
224
```javascript
225
const libReport = require('istanbul-lib-report');
226
227
function generateXMLReport(coverageMap) {
228
const context = libReport.createContext({
229
dir: 'coverage',
230
coverageMap,
231
defaultSummarizer: 'nested'
232
});
233
234
const contentWriter = context.getWriter().writeFile('coverage.xml');
235
const xmlWriter = context.getXMLWriter(contentWriter);
236
237
// XML declaration and root element
238
contentWriter.println('<?xml version="1.0" encoding="UTF-8"?>');
239
xmlWriter.openTag('coverage', {
240
'line-rate': '0.85',
241
'branch-rate': '0.78',
242
timestamp: Math.floor(Date.now() / 1000).toString(),
243
version: '1.0'
244
});
245
246
// Get coverage tree and traverse
247
const tree = context.getTree();
248
tree.visit({
249
onStart() {
250
xmlWriter.openTag('packages');
251
},
252
253
onSummary(node) {
254
if (!node.isRoot()) {
255
const summary = node.getCoverageSummary();
256
xmlWriter.openTag('package', {
257
name: node.getQualifiedName(),
258
'line-rate': (summary.lines.pct / 100).toFixed(2),
259
'branch-rate': (summary.branches.pct / 100).toFixed(2)
260
});
261
xmlWriter.openTag('classes');
262
}
263
},
264
265
onDetail(node) {
266
const summary = node.getCoverageSummary();
267
const fileCoverage = node.getFileCoverage();
268
269
xmlWriter.openTag('class', {
270
name: node.getRelativeName(),
271
filename: node.getQualifiedName(),
272
'line-rate': (summary.lines.pct / 100).toFixed(2),
273
'branch-rate': (summary.branches.pct / 100).toFixed(2),
274
complexity: '0'
275
});
276
277
// Add line information
278
xmlWriter.openTag('lines');
279
Object.entries(fileCoverage.getLineCoverage()).forEach(([lineNum, count]) => {
280
xmlWriter.inlineTag('line', {
281
number: lineNum,
282
hits: count.toString(),
283
branch: 'false'
284
});
285
});
286
xmlWriter.closeTag('lines');
287
288
xmlWriter.closeTag('class');
289
},
290
291
onSummaryEnd(node) {
292
if (!node.isRoot()) {
293
xmlWriter.closeTag('classes');
294
xmlWriter.closeTag('package');
295
}
296
},
297
298
onEnd() {
299
xmlWriter.closeTag('packages');
300
xmlWriter.closeTag('coverage');
301
contentWriter.close();
302
}
303
});
304
}
305
```
306
307
## Types
308
309
```javascript { .api }
310
interface XMLWriter {
311
constructor(contentWriter: ContentWriter): XMLWriter;
312
openTag(name: string, attrs?: Record<string, string>): void;
313
closeTag(name: string): void;
314
inlineTag(name: string, attrs?: Record<string, string>, content?: string): void;
315
closeAll(): void;
316
}
317
318
interface XMLAttributes {
319
[key: string]: string | number;
320
}
321
```