Bug 11559: (followup) Fix import bugs, display/parsing issues
[koha.git] / koha-tmpl / intranet-tmpl / lib / koha / cateditor / marc-record.js
blob1e57904d9c68e692f1bbdd26b8ede711ff766e59
1 /**
2  * Copyright 2015 ByWater Solutions
3  *
4  * This file is part of Koha.
5  *
6  * Koha is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Koha is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Koha; if not, see <http://www.gnu.org/licenses>.
18  */
20 /**
21  * Adapted and cleaned up from biblios.net, which is purportedly under the GPL.
22  * Source: http://git.librarypolice.com/?p=biblios.git;a=blob_plain;f=plugins/marc21editor/marcrecord.js;hb=master
23  *
24  * ISO2709 import/export is cribbed from marcjs, which is under the MIT license.
25  * Source: https://github.com/fredericd/marcjs/blob/master/lib/marcjs.js
26  *
27  * UTF8 encode/decode cribbed from: http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
28  */
30 define( function() {
31     var MARC = {};
33     var _escape_map = {
34         "<": "&lt;",
35         "&": "&amp;",
36         "\"": "&quot;"
37     };
39     function _escape(str) {
40         return str.replace( /[<&"]/, function (c) { return _escape_map[c] } );
41     }
43     function _intpadded(i, digits) {
44         i = i + '';
45         while (i.length < digits) {
46             i = '0' + i;
47         }
48         return i;
49     }
51     function _encode_utf8(s) {
52         return unescape(encodeURIComponent(s));
53     }
55     function _decode_utf8(s) {
56         return decodeURIComponent(escape(s));
57     }
59     MARC.Record = function (fieldlist) {
60         this._fieldlist = fieldlist || [];
61     }
63     $.extend( MARC.Record.prototype, {
64         leader: function(val) {
65             var field = this.field('000');
67             if (val) {
68                 if (field) {
69                     field.subfield( '@', val );
70                 } else {
71                     field = new MARC.Field( '000', '', '', [ [ '@', val ] ] );
72                     this.addFieldGrouped(field);
73                 }
74             } else {
75                 return ( field && field.subfield('@') ) || '     nam a22     7a 4500';
76             }
77         },
79         /**
80          * If a tagnumber is given, returns all fields with that tagnumber.
81          * Otherwise, returns all fields.
82          */
83         fields: function(fieldno) {
84             if (!fieldno) return this._fieldlist;
86             var results = [];
87             for(var i=0; i<this._fieldlist.length; i++){
88                 if( this._fieldlist[i].tagnumber() == fieldno ) {
89                     results.push(this._fieldlist[i]);
90                 }
91             }
93             return results;
94         },
96         /**
97          * Returns the first field with the given tagnumber, or false.
98          */
99         field: function(fieldno) {
100             for(var i=0; i<this._fieldlist.length; i++){
101                 if( this._fieldlist[i].tagnumber() == fieldno ) {
102                     return this._fieldlist[i];
103                 }
104             }
105             return false;
106         },
108         /**
109          * Adds the given MARC.Field to the record, at the end.
110          */
111         addField: function(field) {
112             this._fieldlist.push(field);
113             return true;
114         },
116         /**
117          * Adds the given MARC.Field to the record, at the end of the matching
118          * x00 group. If a record has a 100, 245 and 300 field, for instance, a
119          * 260 field would be added after the 245 field.
120          */
121         addFieldGrouped: function(field) {
122             for ( var i = this._fieldlist.length - 1; i >= 0; i-- ) {
123                 if ( this._fieldlist[i].tagnumber()[0] <= field.tagnumber()[0] ) {
124                     this._fieldlist.splice(i+1, 0, field);
125                     return true;
126                 }
127             }
128             this._fieldlist.push(field);
129             return true;
130         },
132         /**
133          * Removes the first field with the given tagnumber. Returns false if no
134          * such field was found.
135          */
136         removeField: function(fieldno) {
137             for(var i=0; i<this._fieldlist.length; i++){
138                 if( this._fieldlist[i].tagnumber() == fieldno ) {
139                     this._fieldlist.splice(i, 1);
140                     return true;
141                 }
142             }
143             return false;
144         },
146         /**
147          * Check to see if this record contains a field with the given
148          * tagnumber.
149          */
150         hasField: function(fieldno) {
151             for(var i=0; i<this._fieldlist.length; i++){
152                 if( this._fieldlist[i].tagnumber() == fieldno ) {
153                     return true;
154                 }
155             }
156             return false;
157         },
159         toXML: function() {
160             var xml = '<record xmlns="http://www.loc.gov/MARC21/slim">';
161             for(var i=0; i<this._fieldlist.length; i++){
162                 xml += this._fieldlist[i].toXML();
163             }
164             xml += '</record>';
165             return xml;
166         },
168         /**
169          * Truncates this record, and loads in the data from the given MARCXML
170          * document.
171          */
172         loadMARCXML: function(xmldoc) {
173             var record = this;
174             record.xmlSource = xmldoc;
175             this._fieldlist.length = 0;
176             this.leader( $('leader', xmldoc).text() );
177             $('controlfield', xmldoc).each( function(i) {
178                 val = $(this).text();
179                 tagnum = $(this).attr('tag');
180                 record._fieldlist.push( new MARC.Field(tagnum, '', '', [ [ '@', val ] ]) );
181             });
182             $('datafield', xmldoc).each(function(i) {
183                 var value = $(this).text();
184                 var tagnum = $(this).attr('tag');
185                 var ind1 = $(this).attr('ind1') || ' ';
186                 var ind2 = $(this).attr('ind2') || ' ';
187                 var subfields = new Array();
188                 $('subfield', this).each(function(j) {
189                     var sfval = $(this).text();
190                     var sfcode = $(this).attr('code');
191                     subfields.push( [ sfcode, sfval ] );
192                 });
193                 record._fieldlist.push( new MARC.Field(tagnum, ind1, ind2, subfields) );
194             });
195         },
197         toISO2709: function() {
198             var FT = '\x1e', RT = '\x1d', DE = '\x1f';
199             var directory = '',
200                 from = 0,
201                 chunks = ['', ''];
203             $.each( this._fieldlist, function( undef, element ) {
204                 var chunk = '';
205                 var tag = element.tagnumber();
206                 if (tag == '000') {
207                     return;
208                 } else if (tag < '010') {
209                     chunk = element.subfields()[0][1];
210                 } else {
211                     chunk = element.indicators().join('');
212                     $.each( element.subfields(), function( undef, subfield ) {
213                         chunk += DE + subfield[0] + _encode_utf8(subfield[1]);
214                     } );
215                 }
216                 chunk += FT;
217                 chunks.push(chunk);
218                 directory += _intpadded(tag,3) + _intpadded(chunk.length,4) + _intpadded(from,5);
219                 from += chunk.length;
220             });
222             chunks.push(RT);
223             directory += FT;
224             var offset = 24 + 12 * (this._fieldlist.length - 1) + 1;
225             var length = offset + from + 1;
226             var leader = this.leader();
227             leader = _intpadded(length,5) + leader.substr(5,7) + _intpadded(offset,5) +
228                 leader.substr(17);
229             chunks[0] = leader;
230             chunks[1] = directory;
231             return _decode_utf8( chunks.join('') );
232         },
234         loadISO2709: function(data) {
235             // The underlying offsets work on bytes, not characters
236             data = _encode_utf8(data);
238             this._fieldlist.length = 0;
239             this.leader(data.substr(0, 24));
240             var directory_len = parseInt(data.substring(12, 17), 0) - 25,
241                 number_of_tag = directory_len / 12;
242             for (var i = 0; i < number_of_tag; i++) {
243                 var off = 24 + i * 12,
244                     tag = data.substring(off, off+3),
245                     len = parseInt(data.substring(off+3, off+7), 0) - 1,
246                     pos = parseInt(data.substring(off+7, off+12), 0) + 25 + directory_len,
247                     value = data.substring(pos, pos+len);
248                 if ( parseInt(tag) < 10 ) {
249                     this.addField( new MARC.Field( tag, '', '', [ [ '@', value ] ] ) );
250                 } else {
251                     if ( value.indexOf('\x1F') ) { // There are some subfields
252                         var ind1 = value.substr(0, 1), ind2 = value.substr(1, 1);
253                         var subfields = [];
255                         $.each( value.substr(3).split('\x1f'), function( undef, v ) {
256                             if (v.length < 2) return;
257                             subfields.push([v.substr(0, 1), _decode_utf8( v.substr(1) )]);
258                         } );
260                         this.addField( new MARC.Field( tag, ind1, ind2, subfields ) );
261                     }
262                 }
263             }
264         }
265     } );
267     MARC.Field = function(tagnumber, indicator1, indicator2, subfields) {
268         this._tagnumber = tagnumber;
269         this._indicators = [ indicator1, indicator2 ];
270         this._subfields = subfields;
271     };
273     $.extend( MARC.Field.prototype, {
274         tagnumber: function() {
275             return this._tagnumber;
276         },
278         isControlField: function() {
279             return this._tagnumber < '010';
280         },
282         indicator: function(num, val) {
283             if( val != null ) {
284                 this._indicators[num] = val;
285             }
286             return this._indicators[num];
287         },
289         indicators: function() {
290             return this._indicators;
291         },
293         hasSubfield: function(code) {
294             for(var i = 0; i<this._subfields.length; i++) {
295                 if( this._subfields[i][0] == code ) {
296                     return true;
297                 }
298             }
299             return false;
300         },
302         removeSubfield: function(code) {
303             for(var i = 0; i<this._subfields.length; i++) {
304                 if( this._subfields[i][0] == code ) {
305                     this._subfields.splice(i,1);
306                     return true;
307                 }
308             }
309             return false;
310         },
312         subfields: function() {
313             return this._subfields;
314         },
316         addSubfield: function(sf) {
317             this._subfields.push(sf);
318             return true;
319         },
321         addSubfieldGrouped: function(sf) {
322             function _kind( sc ) {
323                 if ( /[a-z]/.test( sc ) ) {
324                     return 0;
325                 } else if ( /[0-9]/.test( sc ) ) {
326                     return 1;
327                 } else {
328                     return 2;
329                 }
330             }
332             for ( var i = this._subfields.length - 1; i >= 0; i-- ) {
333                 if ( i == 0 && _kind( sf[0] ) < _kind( this._subfields[i][0] ) ) {
334                     this._subfields.splice( 0, 0, sf );
335                     return true;
336                 } else if ( _kind( this._subfields[i][0] ) <= _kind( sf[0] )  ) {
337                     this._subfields.splice( i + 1, 0, sf );
338                     return true;
339                 }
340             }
342             this._subfields.push(sf);
343             return true;
344         },
346         subfield: function(code, val) {
347             var sf = '';
348             for(var i = 0; i<this._subfields.length; i++) {
349                 if( this._subfields[i][0] == code ) {
350                     sf = this._subfields[i];
351                     if( val != null ) {
352                         sf[1] = val;
353                     }
354                     return sf[1];
355                 }
356             }
357             return false;
358         },
360         toXML: function() {
361             // decide if it's controlfield of datafield
362             if( this._tagnumber == '000') {
363                 return '<leader>' + _escape( this._subfields[0][1] ) + '</leader>';
364             } else if ( this._tagnumber < '010' ) {
365                 return '<controlfield tag="' + this._tagnumber + '">' + _escape( this._subfields[0][1] ) + '</controlfield>';
366             } else {
367                 var result = '<datafield tag="' + this._tagnumber + '"';
368                 result += ' ind1="' + this._indicators[0] + '"';
369                 result += ' ind2="' + this._indicators[1] + '">';
370                 for( var i = 0; i< this._subfields.length; i++) {
371                     result += '<subfield code="' + this._subfields[i][0] + '">';
372                     result += _escape( this._subfields[i][1] );
373                     result += '</subfield>';
374                 }
375                 result += '</datafield>';
377                 return result;
378             }
379         }
380     } );
382     return MARC;
383 } );