UNPKG

9.33 kBJavaScriptView Raw
1var fs = require('fs');
2var util = require('util');
3
4var iconv = null;
5try {
6 iconv = require('iconv-lite');
7} catch (err) {
8 // If module iconv-lite is not present then use only utf-8 encoding.
9 iconv = null;
10}
11
12var bufferWrapper = require('./bufferwrapper');
13var MarcRecord = require('./record').MarcRecord;
14
15var field = require('./field');
16var MarcVariableField = field.MarcVariableField;
17var MarcControlField = field.MarcControlField;
18var MarcDataField = field.MarcDataField;
19var MarcSubfield = field.MarcSubfield;
20
21/*
22 * The constructor of MARCXML writer.
23 */
24function MarcXmlWriter(options) {
25 if (!(this instanceof MarcXmlWriter)) {
26 return new MarcXmlWriter(options);
27 }
28
29 // File with records in MARCXML format.
30 this.recordsFile = null;
31 // Flag is true when write() can be performed.
32 this.readyToWrite = false;
33 // Position in file.
34 this.position = null;
35
36 // File options.
37 options = options || {};
38 this.options = {
39 // MARC format variation (MARC21, UNIMARC).
40 format: (options.format || 'UNIMARC').toUpperCase(),
41 // Output data encoding.
42 encoding: options.encoding || null,
43 // Write BOM.
44 bom: options.bom || false
45 }
46}
47
48/*
49 * Opens records file by descriptor.
50 */
51MarcXmlWriter.prototype.openFile = function(recordsFile, options) {
52 this.recordsFile = recordsFile;
53 this.readyToWrite = true;
54 this.position = 0;
55
56 options = options || {};
57
58 if (options.hasOwnProperty('format')) {
59 this.options.format = (options.format || 'UNIMARC').toUpperCase();
60 }
61
62 if (options.hasOwnProperty('encoding')) {
63 if (options.encoding && options.encoding !== 'utf-8'
64 && iconv && iconv.encodingExists(options.encoding))
65 {
66 this.options.encoding = options.encoding;
67 } else {
68 this.options.encoding = null;
69 }
70 }
71
72 if (options.hasOwnProperty('bom')) {
73 this.options.bom = Boolean(options.bom);
74 }
75}
76
77/*
78 * Opens records file by name.
79 */
80MarcXmlWriter.prototype.open = function(recordsFileName) {
81 var self = this;
82 var options = arguments.length === 3 ? arguments[1] : undefined;
83 var callback = arguments.length === 3 ? arguments[2] : arguments[1];
84
85 var flags = (options || {}).flags || 'w';
86 var mode = (options || {}).mode || '0666';
87 fs.open(recordsFileName, flags, mode, function(err, recordsFile) {
88 if (err) { return callback(err); }
89 self.openFile(recordsFile, options);
90
91 var header = (self.options.bom ? '\uFEFF' : '')
92 + '<?xml version="1.0" encoding="'
93 + (self.options.encoding || 'utf-8') + '" ?>\n';
94 if (self.options.format == 'MARC21') {
95 header += '<collection xmlns="http://www.loc.gov/MARC21/slim">\n';
96 } else {
97 header += '<collection>\n';
98 }
99 var buffer = self.options.encoding ?
100 iconv.encode(header, self.options.encoding)
101 : bufferWrapper.from(header, 'utf-8');
102 fs.write(self.recordsFile, buffer, 0, buffer.length, null,
103 function(err, written) {
104 self.position += written;
105 callback(err);
106 }
107 );
108 });
109}
110
111/*
112 * Opens records file by name (sync version).
113 */
114MarcXmlWriter.prototype.openSync = function(recordsFileName, options) {
115 var flags = (options || {}).flags || 'w';
116 var mode = (options || {}).mode || '0666';
117 var recordsFile = fs.openSync(recordsFileName, flags, mode);
118 this.openFile(recordsFile, options);
119
120 var header = (this.options.bom ? '\ufeff' : '')
121 + '<?xml version="1.0" encoding="'
122 + (this.options.encoding || 'utf-8') + '" ?>\n';
123 if (this.options.format == 'MARC21') {
124 header += '<collection xmlns="http://www.loc.gov/MARC21/slim">\n';
125 } else {
126 header += '<collection>\n';
127 }
128 var buffer = this.options.encoding ?
129 iconv.encode(header, this.options.encoding)
130 : bufferWrapper.from(header, 'utf-8');
131 var written = fs.writeSync(this.recordsFile, buffer, 0,
132 buffer.length, null);
133 this.position += written;
134}
135
136/*
137 * Closes records file.
138 */
139MarcXmlWriter.prototype.close = function(callback) {
140 var self = this;
141 if (self.recordsFile !== null) {
142 var footer = '</collection>\n';
143 var buffer = self.options.encoding ?
144 iconv.encode(footer, self.options.encoding, {addBOM: false})
145 : bufferWrapper.from(footer, 'utf-8');
146 fs.write(self.recordsFile, buffer, 0, buffer.length, null, function(err) {
147 if (err) { return callback(err); }
148 fs.close(self.recordsFile, function(err) {
149 self.readyToWrite = false;
150 self.recordsFile = null;
151 self.position = null;
152 callback(err);
153 });
154 });
155 }
156}
157
158/*
159 * Closes records file (sync version).
160 */
161MarcXmlWriter.prototype.closeSync = function() {
162 if (this.recordsFile !== null) {
163 var footer = '</collection>\n';
164 var buffer = this.options.encoding ?
165 iconv.encode(footer, this.options.encoding, {addBOM: false})
166 : bufferWrapper.from(footer, 'utf-8');
167 fs.writeSync(this.recordsFile, buffer, 0, buffer.length, null);
168
169 fs.closeSync(this.recordsFile);
170 this.readyToWrite = false;
171 this.recordsFile = null;
172 this.position = null;
173 }
174}
175
176/*
177 * Writes record to the file.
178 */
179MarcXmlWriter.prototype.write = function(record, callback) {
180 var self = this;
181 if (self.recordsFile === null) {
182 return callback(new Error('records file must be opened'));
183 }
184
185 var marcXmlRecord = MarcXmlWriter.recordToMarcXml(record, self.options);
186 var buffer = self.options.encoding ?
187 iconv.encode(marcXmlRecord, self.options.encoding, {addBOM: false})
188 : bufferWrapper.from(marcXmlRecord, 'utf-8');
189 fs.write(self.recordsFile, buffer, 0, buffer.length, null,
190 function(err, written) {
191 self.position += written;
192 callback(err);
193 }
194 );
195}
196
197/*
198 * Writes record to the file (sync version).
199 */
200MarcXmlWriter.prototype.writeSync = function(record) {
201 if (this.recordsFile === null) {
202 throw new Error('records file must be opened');
203 }
204
205 var marcXmlRecord = MarcXmlWriter.recordToMarcXml(record, this.options);
206 var buffer = this.options.encoding ?
207 iconv.encode(marcXmlRecord, this.options.encoding, {addBOM: false})
208 : bufferWrapper.from(marcXmlRecord, 'utf-8');
209 var written = fs.writeSync(this.recordsFile, buffer, 0,
210 buffer.length, null);
211 this.position += written;
212}
213
214/*
215 * Returns current position in file.
216 */
217MarcXmlWriter.prototype.getPosition = function() {
218 return this.position;
219}
220
221/*
222 * Converts record to MARCXML string.
223 */
224MarcXmlWriter.recordToMarcXml = function(record, options) {
225 // Initialize elements of the MARCXML record.
226 var elements = [];
227
228 // Append leader.
229 var leader = record.getLeader();
230 elements.push([1, '<record>']);
231 elements.push([2, '<leader>%s</leader>', leader]);
232
233 // Iterate fields.
234 var fields = record.getVariableFields();
235 for (var i = 0; i < fields.length; i++) {
236 var field = fields[i];
237 MarcXmlWriter.fieldToMarcXml(field, elements, options, 2);
238 }
239
240 elements.push([1, '</record>']);
241
242 // Format MARCXML record.
243 var marcXmlRecord = '';
244 for (var i = 0; i < elements.length; i++) {
245 var element = elements[i];
246 var elementLevel = element[0];
247 var elementData = element.slice(1).map(function(value, index) {
248 if (index === 0) { return value; }
249 return value
250 .replace(/&/g, '&amp;')
251 .replace(/</g, '&lt;')
252 .replace(/>/g, '&gt;');
253 });
254
255 elementString = util.format.apply(null, elementData);
256 marcXmlRecord += Array(elementLevel + 1).join(' ') + elementString + '\n';
257 }
258
259 return marcXmlRecord;
260}
261
262/*
263 * Converts field to MARCXML elements.
264 */
265MarcXmlWriter.fieldToMarcXml = function(field, elements, options, level) {
266 if (field.isControlField()) {
267 // Append control field.
268 elements.push([level, '<controlfield tag="%s">%s</controlfield>',
269 field.tag, field.data]);
270 } else {
271 // Append data field.
272 elements.push([level, '<datafield tag="%s" ind1="%s" ind2="%s">',
273 field.tag, field.ind1, field.ind2]);
274
275 var subfields = field.getSubfields();
276 for (var i = 0; i < subfields.length; i++) {
277 var subfield = subfields[i];
278 MarcXmlWriter.subfieldToMarcXml(subfield, elements, options, level + 1);
279 }
280
281 elements.push([level, '</datafield>']);
282 }
283}
284
285/*
286 * Converts subfield to MARCXML elements.
287 */
288MarcXmlWriter.subfieldToMarcXml = function(subfield, elements, options,
289 level)
290{
291 if (!subfield.isEmbeddedField()) {
292 elements.push([level, '<subfield code="%s">%s</subfield>',
293 subfield.code, subfield.data]);
294 return;
295 }
296
297 if (options.format === 'UNIMARC') {
298 elements.push([level, '<s%s>', subfield.code]);
299 MarcXmlWriter.fieldToMarcXml(subfield.data, elements, options, level + 1);
300 elements.push([level, '</s%s>', subfield.code]);
301 return;
302 }
303
304 var embeddedField = subfield.data;
305 if (embeddedField.isControlField()) {
306 elements.push([level, '<subfield code="%s">%s%s</subfield>',
307 subfield.code, embeddedField.tag, embeddedField.data]);
308 return;
309 }
310
311 elements.push([level, '<subfield code="%s">%s%s%s</subfield>',
312 subfield.code, embeddedField.tag, embeddedField.ind1, embeddedField.ind2]);
313 var embeddedSubfields = embeddedField.getSubfields();
314 for (var i = 0; i < embeddedSubfields.length; i++) {
315 var embeddedSubfield = embeddedSubfields[i];
316 MarcXmlWriter.subfieldToMarcXml(
317 embeddedSubfield, elements, options, level);
318 }
319}
320
321module.exports = {
322 MarcXmlWriter: MarcXmlWriter
323};