2 * Copyright 2015 ByWater Solutions
4 * This file is part of Koha.
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.
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.
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>.
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
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
27 * UTF8 encode/decode cribbed from: http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
39 function _escape(str) {
40 return str.replace( /[<&"]/, function (c) { return _escape_map[c] } );
43 function _intpadded(i, digits) {
45 while (i.length < digits) {
51 function _encode_utf8(s) {
52 return unescape(encodeURIComponent(s));
55 function _decode_utf8(s) {
56 return decodeURIComponent(escape(s));
59 MARC.Record = function (fieldlist) {
60 this._fieldlist = fieldlist || [];
63 $.extend( MARC.Record.prototype, {
64 leader: function(val) {
65 var field = this.field('000');
69 field.subfield( '@', val );
71 field = new MARC.Field( '000', '', '', [ [ '@', val ] ] );
72 this.addFieldGrouped(field);
75 return ( field && field.subfield('@') ) || ' nam a22 7a 4500';
80 * If a tagnumber is given, returns all fields with that tagnumber.
81 * Otherwise, returns all fields.
83 fields: function(fieldno) {
84 if (!fieldno) return this._fieldlist;
87 for(var i=0; i<this._fieldlist.length; i++){
88 if( this._fieldlist[i].tagnumber() == fieldno ) {
89 results.push(this._fieldlist[i]);
97 * Returns the first field with the given tagnumber, or false.
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];
109 * Adds the given MARC.Field to the record, at the end.
111 addField: function(field) {
112 this._fieldlist.push(field);
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.
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);
128 this._fieldlist.push(field);
133 * Removes the first field with the given tagnumber. Returns false if no
134 * such field was found.
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);
147 * Check to see if this record contains a field with the given
150 hasField: function(fieldno) {
151 for(var i=0; i<this._fieldlist.length; i++){
152 if( this._fieldlist[i].tagnumber() == fieldno ) {
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();
169 * Truncates this record, and loads in the data from the given MARCXML
172 loadMARCXML: function(xmldoc) {
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 ] ]) );
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 ] );
193 record._fieldlist.push( new MARC.Field(tagnum, ind1, ind2, subfields) );
197 toISO2709: function() {
198 var FT = '\x1e', RT = '\x1d', DE = '\x1f';
203 $.each( this._fieldlist, function( undef, element ) {
205 var tag = element.tagnumber();
208 } else if (tag < '010') {
209 chunk = element.subfields()[0][1];
211 chunk = element.indicators().join('');
212 $.each( element.subfields(), function( undef, subfield ) {
213 chunk += DE + subfield[0] + _encode_utf8(subfield[1]);
218 directory += _intpadded(tag,3) + _intpadded(chunk.length,4) + _intpadded(from,5);
219 from += chunk.length;
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) +
230 chunks[1] = directory;
231 return _decode_utf8( chunks.join('') );
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 ] ] ) );
251 if ( value.indexOf('\x1F') ) { // There are some subfields
252 var ind1 = value.substr(0, 1), ind2 = value.substr(1, 1);
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) )]);
260 this.addField( new MARC.Field( tag, ind1, ind2, subfields ) );
267 MARC.Field = function(tagnumber, indicator1, indicator2, subfields) {
268 this._tagnumber = tagnumber;
269 this._indicators = [ indicator1, indicator2 ];
270 this._subfields = subfields;
273 $.extend( MARC.Field.prototype, {
274 tagnumber: function() {
275 return this._tagnumber;
278 isControlField: function() {
279 return this._tagnumber < '010';
282 indicator: function(num, val) {
284 this._indicators[num] = val;
286 return this._indicators[num];
289 indicators: function() {
290 return this._indicators;
293 hasSubfield: function(code) {
294 for(var i = 0; i<this._subfields.length; i++) {
295 if( this._subfields[i][0] == code ) {
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);
312 subfields: function() {
313 return this._subfields;
316 addSubfield: function(sf) {
317 this._subfields.push(sf);
321 addSubfieldGrouped: function(sf) {
322 function _kind( sc ) {
323 if ( /[a-z]/.test( sc ) ) {
325 } else if ( /[0-9]/.test( sc ) ) {
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 );
336 } else if ( _kind( this._subfields[i][0] ) <= _kind( sf[0] ) ) {
337 this._subfields.splice( i + 1, 0, sf );
342 this._subfields.push(sf);
346 subfield: function(code, val) {
348 for(var i = 0; i<this._subfields.length; i++) {
349 if( this._subfields[i][0] == code ) {
350 sf = this._subfields[i];
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>';
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>';
375 result += '</datafield>';