Bug 11559: (followup) Fix import bugs, display/parsing issues
[koha.git] / koha-tmpl / intranet-tmpl / lib / koha / cateditor / marc-mode.js
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  */
19
20 // Expected format: 245 _ 1 $a Pizza |c 34ars
21
22 CodeMirror.defineMode( 'marc', function( config, modeConfig ) {
23     modeConfig.nonRepeatableTags = modeConfig.nonRepeatableTags || {};
24     modeConfig.nonRepeatableSubfields = modeConfig.nonRepeatableSubfields || {};
25
26     return {
27         startState: function( prevState ) {
28             var state = prevState || {};
29
30             if ( !prevState ) {
31                 state.seenTags = {};
32             }
33
34             state.indicatorNeeded = false;
35             state.subAllowed = true;
36             state.subfieldCode = undefined;
37             state.tagNumber = undefined;
38             state.seenSubfields = {};
39
40             return state;
41         },
42         copyState: function( prevState ) {
43             var result = $.extend( {}, prevState );
44             result.seenTags = $.extend( {}, prevState.seenTags );
45             result.seenSubfields = $.extend( {}, prevState.seenSubfields );
46
47             return result;
48         },
49         token: function( stream, state ) {
50             var match;
51             // First, try to match some kind of valid tag
52             if ( stream.sol() ) {
53                 this.startState( state );
54                 if ( match = stream.match( /[0-9A-Za-z]+/ ) ) {
55                     match = match[0];
56                     if ( match.length != 3 ) {
57                         if ( stream.eol() && match.length < 3 ) {
58                             // Don't show error for incomplete number
59                             return 'tagnumber';
60                         } else {
61                             stream.skipToEnd();
62                             return 'error';
63                         }
64                     }
65
66                     state.tagNumber = match;
67                     if ( state.tagNumber < '010' ) {
68                         // Control field, no subfields or indicators
69                         state.subAllowed = false;
70                     }
71
72                     if ( state.seenTags[state.tagNumber] && modeConfig.nonRepeatableTags[state.tagNumber] ) {
73                         return 'bad-tagnumber';
74                     } else {
75                         state.seenTags[state.tagNumber] = true;
76                         return 'tagnumber';
77                     }
78                 } else {
79                     stream.skipToEnd();
80                     return 'error';
81                 }
82             }
83
84             // Don't need to do anything
85             if ( stream.eol() ) {
86                 return;
87             }
88
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';
93                 } else {
94                     stream.skipToEnd();
95                     return 'error';
96                 }
97             }
98
99             // For a normal field, check for correct indicators and spacing
100             if ( stream.pos < 8 && state.subAllowed ) {
101                 switch ( stream.pos ) {
102                     case 3:
103                     case 5:
104                     case 7:
105                         if ( stream.next() == ' ' ) {
106                             return 'required-space';
107                         } else {
108                             stream.skipToEnd();
109                             return 'error';
110                         }
111                     case 4:
112                     case 6:
113                         if ( /[0-9A-Za-z_]/.test( stream.next() ) ) {
114                             return 'indicator';
115                         } else {
116                             stream.skipToEnd();
117                             return 'error';
118                         }
119                 }
120             }
121
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.
128                     //
129                     // This is required to keep the contents matching from stepping on the end-space
130                     // matching.
131                     if ( stream.match( /[ \t]+$/ ) ) {
132                         return 'end-space';
133                     } else if ( stream.match( /[^ \t$|ǂ‡]+/ ) || stream.match( /[ \t]+/ ) ) {
134                         return;
135                     }
136                 }
137
138                 if ( stream.eat( /[$|ǂ‡]/ ) ) {
139                     var subfieldCode;
140                     if ( ( subfieldCode = stream.eat( /[a-zA-Z0-9%]/ ) ) && stream.eat( ' ' ) ) {
141                         state.subfieldCode = subfieldCode;
142                         if ( state.seenSubfields[state.subfieldCode] && ( modeConfig.nonRepeatableSubfields[state.tagNumber] || {} )[state.subfieldCode] ) {
143                             return 'bad-subfieldcode';
144                         } else {
145                             state.seenSubfields[state.subfieldCode] = true;
146                             return 'subfieldcode';
147                         }
148                     }
149                 }
150
151                 if ( stream.pos < 11 && ( !stream.eol() || stream.pos == 8 ) ) {
152                     stream.skipToEnd();
153                     return 'error';
154                 }
155             } else {
156                 // Match space at end of line
157                 if ( stream.match( /[ \t]+$/ ) ) {
158                     return 'end-space';
159                 } else {
160                     stream.match( /[ \t]+/ );
161                 }
162
163                 stream.match( /[^ \t]+/ );
164                 return;
165             }
166         }
167     };
168 } );