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>.
20 // Expected format: 245 _ 1 ‡aPizza ‡c34ars
22 CodeMirror.defineMode( 'marc', function( config, modeConfig ) {
23 modeConfig.nonRepeatableTags = modeConfig.nonRepeatableTags || {};
24 modeConfig.nonRepeatableSubfields = modeConfig.nonRepeatableSubfields || {};
27 startState: function( prevState ) {
28 var state = prevState || {};
34 state.indicatorNeeded = false;
35 state.subAllowed = true;
36 state.subfieldCode = undefined;
37 state.tagNumber = undefined;
38 state.seenSubfields = {};
42 copyState: function( prevState ) {
43 var result = $.extend( {}, prevState );
44 result.seenTags = $.extend( {}, prevState.seenTags );
45 result.seenSubfields = $.extend( {}, prevState.seenSubfields );
49 token: function( stream, state ) {
51 // First, try to match some kind of valid tag
53 this.startState( state );
54 if ( match = stream.match( /[0-9A-Za-z]+/ ) ) {
56 if ( match.length != 3 ) {
57 if ( stream.eol() && match.length < 3 ) {
58 // Don't show error for incomplete number
66 state.tagNumber = match;
67 if ( state.tagNumber < '010' ) {
68 // Control field, no subfields or indicators
69 state.subAllowed = false;
72 if ( state.seenTags[state.tagNumber] && modeConfig.nonRepeatableTags[state.tagNumber] ) {
73 return 'bad-tagnumber';
75 state.seenTags[state.tagNumber] = true;
84 // Don't need to do anything
89 // Check for the correct space after the tag number for a control field
90 if ( !state.subAllowed && stream.pos == 3 ) {
91 if ( stream.next() == ' ' ) {
92 return 'required-space';
99 // For a normal field, check for correct indicators and spacing
100 if ( stream.pos < 8 && state.subAllowed ) {
101 switch ( stream.pos ) {
105 if ( stream.next() == ' ' ) {
106 return 'required-space';
113 if ( /[0-9A-Za-z_]/.test( stream.next() ) ) {
122 // Otherwise, we're after the start of the line.
123 if ( state.subAllowed ) {
124 // If we don't have to match a subfield, try to consume text.
125 if ( stream.pos != 8 ) {
126 // Try to match space at the end of the line, then everything but spaces, and as
127 // a final fallback, only spaces.
129 // This is required to keep the contents matching from stepping on the end-space
131 if ( stream.match( /[ \t]+$/ ) ) {
133 } else if ( stream.match( /\s{2,}/ ) ) {
134 return 'double-space';
135 } else if ( stream.match( /[^ \t‡]+/ ) || stream.match( /[ \t]+/ ) ) {
140 if ( stream.eat( '‡' ) ) {
142 if ( ( subfieldCode = stream.eat( /[a-zA-Z0-9%]/ ) ) ) {
143 state.subfieldCode = subfieldCode;
144 if ( state.seenSubfields[state.subfieldCode] && ( modeConfig.nonRepeatableSubfields[state.tagNumber] || {} )[state.subfieldCode] ) {
145 return 'bad-subfieldcode';
147 state.seenSubfields[state.subfieldCode] = true;
148 return 'subfieldcode';
153 if ( stream.pos < 11 && ( !stream.eol() || stream.pos == 8 ) ) {
158 // Match space at end of line
159 if ( stream.match( /[ \t]+$/ ) ) {
161 } else if ( stream.match( /\s{2,}/ ) ) {
162 return 'double-space';
164 stream.match( /[ \t]+/ );
167 stream.match( /[^ \t]+/ );