5c287a8931
This followup introduces a major change; instead of subfields starting with '$<code><space>', they now start with '‡<code>'. The double-cross character can be typed with Ctrl-D. It also fixes the following: * Add UUID.pm dependency * Remove debugging call * Fix toLocaleFormat error reported by Nick Clemens * Ignore subfields that are marked as unrepeatable/mandatory AND ignored (tab is -1) * Mention lack of support for UNIMARC/NORMARC fixed fields in system preferences screen * Confirm when user creates new record and current record is modified * Perform better when importing gigantic record dump * Show "Edit" instead of "Import" and allow direct editing for local catalog records in search screen * Add "Keyboard shortcuts" help button to toolbar Signed-off-by: Nick Clemens <nick@quecheelibrary.org> Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
168 lines
6.2 KiB
JavaScript
168 lines
6.2 KiB
JavaScript
/**
|
|
* Copyright 2015 ByWater Solutions
|
|
*
|
|
* This file is part of Koha.
|
|
*
|
|
* Koha is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Koha is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Koha; if not, see <http://www.gnu.org/licenses>.
|
|
*/
|
|
|
|
// Expected format: 245 _ 1 ‡aPizza ‡c34ars
|
|
|
|
CodeMirror.defineMode( 'marc', function( config, modeConfig ) {
|
|
modeConfig.nonRepeatableTags = modeConfig.nonRepeatableTags || {};
|
|
modeConfig.nonRepeatableSubfields = modeConfig.nonRepeatableSubfields || {};
|
|
|
|
return {
|
|
startState: function( prevState ) {
|
|
var state = prevState || {};
|
|
|
|
if ( !prevState ) {
|
|
state.seenTags = {};
|
|
}
|
|
|
|
state.indicatorNeeded = false;
|
|
state.subAllowed = true;
|
|
state.subfieldCode = undefined;
|
|
state.tagNumber = undefined;
|
|
state.seenSubfields = {};
|
|
|
|
return state;
|
|
},
|
|
copyState: function( prevState ) {
|
|
var result = $.extend( {}, prevState );
|
|
result.seenTags = $.extend( {}, prevState.seenTags );
|
|
result.seenSubfields = $.extend( {}, prevState.seenSubfields );
|
|
|
|
return result;
|
|
},
|
|
token: function( stream, state ) {
|
|
var match;
|
|
// First, try to match some kind of valid tag
|
|
if ( stream.sol() ) {
|
|
this.startState( state );
|
|
if ( match = stream.match( /[0-9A-Za-z]+/ ) ) {
|
|
match = match[0];
|
|
if ( match.length != 3 ) {
|
|
if ( stream.eol() && match.length < 3 ) {
|
|
// Don't show error for incomplete number
|
|
return 'tagnumber';
|
|
} else {
|
|
stream.skipToEnd();
|
|
return 'error';
|
|
}
|
|
}
|
|
|
|
state.tagNumber = match;
|
|
if ( state.tagNumber < '010' ) {
|
|
// Control field, no subfields or indicators
|
|
state.subAllowed = false;
|
|
}
|
|
|
|
if ( state.seenTags[state.tagNumber] && modeConfig.nonRepeatableTags[state.tagNumber] ) {
|
|
return 'bad-tagnumber';
|
|
} else {
|
|
state.seenTags[state.tagNumber] = true;
|
|
return 'tagnumber';
|
|
}
|
|
} else {
|
|
stream.skipToEnd();
|
|
return 'error';
|
|
}
|
|
}
|
|
|
|
// Don't need to do anything
|
|
if ( stream.eol() ) {
|
|
return;
|
|
}
|
|
|
|
// Check for the correct space after the tag number for a control field
|
|
if ( !state.subAllowed && stream.pos == 3 ) {
|
|
if ( stream.next() == ' ' ) {
|
|
return 'required-space';
|
|
} else {
|
|
stream.skipToEnd();
|
|
return 'error';
|
|
}
|
|
}
|
|
|
|
// For a normal field, check for correct indicators and spacing
|
|
if ( stream.pos < 8 && state.subAllowed ) {
|
|
switch ( stream.pos ) {
|
|
case 3:
|
|
case 5:
|
|
case 7:
|
|
if ( stream.next() == ' ' ) {
|
|
return 'required-space';
|
|
} else {
|
|
stream.skipToEnd();
|
|
return 'error';
|
|
}
|
|
case 4:
|
|
case 6:
|
|
if ( /[0-9A-Za-z_]/.test( stream.next() ) ) {
|
|
return 'indicator';
|
|
} else {
|
|
stream.skipToEnd();
|
|
return 'error';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, we're after the start of the line.
|
|
if ( state.subAllowed ) {
|
|
// If we don't have to match a subfield, try to consume text.
|
|
if ( stream.pos != 8 ) {
|
|
// Try to match space at the end of the line, then everything but spaces, and as
|
|
// a final fallback, only spaces.
|
|
//
|
|
// This is required to keep the contents matching from stepping on the end-space
|
|
// matching.
|
|
if ( stream.match( /[ \t]+$/ ) ) {
|
|
return 'end-space';
|
|
} else if ( stream.match( /[^ \t‡]+/ ) || stream.match( /[ \t]+/ ) ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( stream.eat( '‡' ) ) {
|
|
var subfieldCode;
|
|
if ( ( subfieldCode = stream.eat( /[a-zA-Z0-9%]/ ) ) ) {
|
|
state.subfieldCode = subfieldCode;
|
|
if ( state.seenSubfields[state.subfieldCode] && ( modeConfig.nonRepeatableSubfields[state.tagNumber] || {} )[state.subfieldCode] ) {
|
|
return 'bad-subfieldcode';
|
|
} else {
|
|
state.seenSubfields[state.subfieldCode] = true;
|
|
return 'subfieldcode';
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( stream.pos < 11 && ( !stream.eol() || stream.pos == 8 ) ) {
|
|
stream.skipToEnd();
|
|
return 'error';
|
|
}
|
|
} else {
|
|
// Match space at end of line
|
|
if ( stream.match( /[ \t]+$/ ) ) {
|
|
return 'end-space';
|
|
} else {
|
|
stream.match( /[ \t]+/ );
|
|
}
|
|
|
|
stream.match( /[^ \t]+/ );
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
} );
|