View | Details | Raw Unified | Return to bug 6707
Collapse All | Expand All

(-)a/C4/Biblio.pm (+253 lines)
Lines 126-131 BEGIN { Link Here
126
      &TransformHtmlToMarc2
126
      &TransformHtmlToMarc2
127
      &TransformHtmlToMarc
127
      &TransformHtmlToMarc
128
      &TransformHtmlToXml
128
      &TransformHtmlToXml
129
      &TransformTextToMarc
129
      &PrepareItemrecordDisplay
130
      &PrepareItemrecordDisplay
130
      &GetNoZebraIndexes
131
      &GetNoZebraIndexes
131
    );
132
    );
Lines 2097-2102 sub TransformHtmlToMarc { Link Here
2097
    return $record;
2098
    return $record;
2098
}
2099
}
2099
2100
2101
=item TransformTextToMarc
2102
2103
	$record = TransformTextToMarc($text[, existing_record => $existing_record, debug => $debug]);
2104
2105
Parses a textual representation of MARC data into a MARC::Record. If an error
2106
occurs, will die(); this can be caught with eval { ... } if ($@) { ... }
2107
2108
$text should be a series of lines with the following format:
2109
2110
Control fields: 005 20080303040352.1
2111
Data fields: 245 10 $a The $1,000,000 problem / $c Robert Biggs.
2112
2113
Indicators are optional. Subfields are delimited by | or $, and both of these
2114
characters are allowed in subfield contents as long as they are not followed by
2115
a number/digit and a space.
2116
2117
If $existing_record is defined as a MARC::Record, TransformTextToMarc will place
2118
parsed fields into it and return it, rather than creating a new MARC::Record.
2119
2120
If $debug is true, then the parser will output very verbose debugging
2121
information to stdout.
2122
2123
=cut
2124
2125
sub TransformTextToMarc {
2126
	# A non-deterministic-finite-state-machine based parser for a textual MARC
2127
	# format.
2128
	#
2129
	# Allowable contents of tag numbers, indicators and subfield codes are
2130
	# based on the MARCXML standard.
2131
	#
2132
	# While this is a mostly conventional FSM, it has two major peculiarities:
2133
	# * A buffer, separate from the current character, that is manually added
2134
	#   to by each state.
2135
	# * Two methods of transitioning between states; jumping, which preserves
2136
	#   the buffer, and switching, which does not.
2137
2138
	our ($text, %options) = @_;
2139
2140
	%options = ((
2141
		existing_record => MARC::Record->new(),
2142
		debug => 0,
2143
		strip_whitespace => 1,
2144
	), %options);
2145
2146
	my $record = $options{'existing_record'};
2147
2148
	$text =~ s/(\r\n)|\r/\n/g;
2149
2150
	our $state = 'start';
2151
	our $last_state = '';
2152
	our $char = '';
2153
	our $line = 1;
2154
2155
	our $field = undef;
2156
	our $buffer = '';
2157
	our $tag = '';
2158
	our $indicator = '';
2159
	our $subfield_code = '';
2160
2161
	my %states = (
2162
		start => sub {
2163
			# Start of line. All buffers are empty.
2164
			if ($char =~ /[0-9]/) {
2165
				$buffer .= $char;
2166
				jump_state('tag_id');
2167
			} elsif ($char ne "\n") {
2168
				error("expected MARC tag number at start of line, got '$char'");
2169
			}
2170
		},
2171
		tag_id => sub {
2172
			# Jumped to from start, so buffer has first character of tag
2173
			# Allows letters in second and third digits of tag number
2174
			if (length($buffer) < 3) {
2175
				if ($char =~ /[0-9a-zA-Z]/) {
2176
					$buffer .= $char;
2177
				} else {
2178
					error("expected digit or letter, got '$char' in tag number");
2179
				}
2180
			} elsif ($char eq ' ') {
2181
				$tag = $buffer;
2182
				if ($tag =~ /^00/) {
2183
					set_state('control_field_content');
2184
				} else {
2185
					set_state('indicator');
2186
				}
2187
			} else {
2188
				error("expected whitespace after tag number, got '$char'");
2189
			}
2190
		},
2191
		indicator => sub {
2192
			# Parses optional indicator, composed of digits or lowercase letters
2193
			# Will consume leading $ or | of subfield if no indicator; otherwise
2194
			# expecting_subfield will do so
2195
			if (length($buffer) == 0) {
2196
				if ($char =~ /[\$\|]/) {
2197
					$indicator = '  ';
2198
					set_state('expecting_subfield_code');
2199
				} elsif ($char =~ /[0-9a-z_ ]/) {
2200
					$buffer .= $char;
2201
				} else {
2202
					error("expected either subfield or indicator after tag number, got '$char'");
2203
				}
2204
			} elsif (length($buffer) < 2) {
2205
				if ($char =~ /[0-9a-z_ ]/) {
2206
					$buffer .= $char;
2207
				} else {
2208
					error("expected digit, letter or blank in indicator, got '$char'");
2209
				}
2210
			} elsif ($char eq ' ') {
2211
				$indicator = $buffer;
2212
				$indicator =~ s/_/ /g;
2213
				set_state('expecting_subfield');
2214
			} else {
2215
				error("expected space after indicator, got '$char'");
2216
			}
2217
		},
2218
		expecting_subfield => sub {
2219
			if ($char =~ /[\$\|]/) {
2220
				set_state('expecting_subfield_code');
2221
			} else {
2222
				error("expected \$ or | after indicator or tag number, got '$char'");
2223
			}
2224
		},
2225
		expecting_subfield_code => sub {
2226
			if ($char =~ /[a-z0-9]/) {
2227
				$subfield_code = $char;
2228
				set_state('expecting_subfield_space');
2229
			} else {
2230
				error("expected number or letter in subfield code, got '$char'");
2231
			}
2232
		},
2233
		expecting_subfield_space => sub {
2234
			if ($char eq ' ') {
2235
				set_state('subfield_content');
2236
			} else {
2237
				error("expected space after subfield code, got '$char'");
2238
			}
2239
		},
2240
		control_field_content => sub {
2241
			if ($char eq "\n") {
2242
				if ($tag eq '000') {
2243
					$record->leader($buffer);
2244
				} else {
2245
					$record->append_fields(MARC::Field->new($tag, $buffer));
2246
				}
2247
				$tag = '';
2248
				set_state('start');
2249
			} else {
2250
				$buffer .= $char;
2251
			}
2252
		},
2253
		subfield_content => sub {
2254
			# Handles both additional subfields and inserting last subfield
2255
			if ($char =~ /[\$\|]/) {
2256
				$buffer .= $char;
2257
				jump_state('subfield_code');
2258
			} elsif ($char eq "\n") {
2259
				$buffer =~ s/(^\s+|\s+$)//g if ($options{'strip_whitespace'});
2260
				if ($field) {
2261
					$field->add_subfields($subfield_code, $buffer);
2262
				} else {
2263
					$field = MARC::Field->new($tag, substr($indicator, 0, 1), substr($indicator, 1), $subfield_code, $buffer);
2264
				}
2265
				$record->append_fields($field);
2266
2267
				undef $field;
2268
				$tag = '';
2269
				$line++;
2270
2271
				set_state('start');
2272
			} else {
2273
				$buffer .= $char;
2274
			}
2275
		},
2276
		# subfield_code and subfield_space both jump to subfield_content if
2277
		# they do not find the expected format, allowing strings like
2278
		# '245 $a The meaning of the $ sign' and '020 $a ... $c $10.00' to
2279
		# parse correctly
2280
		subfield_code => sub {
2281
			$buffer .= $char;
2282
2283
			if ($char =~ /[a-z0-9]/) {
2284
				jump_state('subfield_space');
2285
			} elsif ($char eq "\n") {
2286
				error("Unexpected newline in subfield code");
2287
			} else {
2288
				jump_state('subfield_content');
2289
			}
2290
		},
2291
		subfield_space => sub {
2292
			# This has to do some manipulation of the buffer to ensure that the
2293
			# ending '$[a-z0-9] ' does not get inserted into the subfield
2294
			# contents
2295
			if ($char eq ' ') {
2296
				my $contents = substr($buffer, 0, -3);
2297
				$contents =~ s/(^\s+|\s+$)//g if ($options{'strip_whitespace'});
2298
				if ($field) {
2299
					$field->add_subfields($subfield_code, $contents);
2300
				} else {
2301
					$field = MARC::Field->new($tag, substr($indicator, 0, 1), substr($indicator, 1), $subfield_code, $contents);
2302
				}
2303
2304
				$subfield_code = substr($buffer, -1);
2305
				set_state('subfield_content');
2306
			} else {
2307
				$buffer .= $char;
2308
				jump_state('subfield_content');
2309
			}
2310
		}
2311
	);
2312
2313
	sub set_state {
2314
		my $new_state = shift;
2315
2316
		print STDERR "$state -> $new_state (buffer was '$buffer'[" . length($buffer) . "])\n" if ($options{'debug'});
2317
2318
		$buffer = '';
2319
		$last_state = $state;
2320
		$state = $new_state;
2321
	}
2322
2323
	sub jump_state {
2324
		my $new_state = shift;
2325
2326
		print STDERR "$state -- $new_state (buffer is '$buffer'[" . length($buffer) . "])\n" if ($options{'debug'});
2327
2328
		$last_state = $state;
2329
		$state = $new_state;
2330
	}
2331
2332
	sub error {
2333
		my $text = shift;
2334
		$text =~ s/\n/newline/gm;
2335
2336
		die "Error on line $line: $text\n";
2337
	}
2338
2339
	for $char (split '', $text) {
2340
		print STDERR "running $state with " . ($char eq "\n" ? "line-break" : "'$char'") . " and buffer '$buffer' (" . length($buffer) . " chars)\n" if ($options{'debug'} >= 2);
2341
		$states{$state}->();
2342
	}
2343
2344
	if ($char ne "\n") {
2345
		print STDERR "running $state at end\n" if ($options{'debug'});
2346
		$char = "\n";
2347
		$states{$state}->();
2348
	}
2349
2350
	return $record;
2351
}
2352
2100
# cache inverted MARC field map
2353
# cache inverted MARC field map
2101
our $inverted_field_map;
2354
our $inverted_field_map;
2102
2355
(-)a/C4/Koha.pm (-1 / +17 lines)
Lines 38-44 BEGIN { Link Here
38
		&slashifyDate
38
		&slashifyDate
39
		&subfield_is_koha_internal_p
39
		&subfield_is_koha_internal_p
40
		&GetPrinters &GetPrinter
40
		&GetPrinters &GetPrinter
41
		&GetItemTypes &getitemtypeinfo
41
		&GetItemTypes &GetItemTypeList &getitemtypeinfo
42
		&GetCcodes
42
		&GetCcodes
43
		&GetSupportName &GetSupportList
43
		&GetSupportName &GetSupportList
44
		&get_itemtypeinfos_of
44
		&get_itemtypeinfos_of
Lines 253-258 sub GetItemTypes { Link Here
253
    return ( \%itemtypes );
253
    return ( \%itemtypes );
254
}
254
}
255
255
256
sub GetItemTypeList {
257
	my ( $selected ) = @_;
258
    my $itemtypes = GetItemTypes;
259
    my @itemtypesloop;
260
261
    foreach my $itemtype ( sort { $itemtypes->{$a}->{'description'} cmp $itemtypes->{$b}->{'description'} } keys( %$itemtypes ) ) {
262
        push @itemtypesloop, {
263
			value => $itemtype,
264
            selected => ( $itemtype eq $selected ),
265
            description => $itemtypes->{$itemtype}->{'description'},
266
        };
267
    }
268
269
	return \@itemtypesloop;
270
}
271
256
sub get_itemtypeinfos_of {
272
sub get_itemtypeinfos_of {
257
    my @itemtypes = @_;
273
    my @itemtypes = @_;
258
274
(-)a/cataloguing/addbiblio-text.pl (+635 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2008 LibLime
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along with
17
# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18
# Suite 330, Boston, MA  02111-1307 USA
19
20
use strict;
21
use CGI;
22
use C4::Output qw(:html :ajax);
23
use C4::Output::JSONStream;
24
use JSON;
25
use C4::Auth;
26
use C4::Biblio;
27
use C4::Search;
28
use C4::AuthoritiesMarc;
29
use C4::Context;
30
use MARC::Record;
31
use MARC::Field;
32
use C4::Log;
33
use C4::Koha;    # XXX subfield_is_koha_internal_p
34
use C4::Branch;    # XXX subfield_is_koha_internal_p
35
use C4::ClassSource;
36
use C4::ImportBatch;
37
use C4::Charset;
38
39
use Date::Calc qw(Today);
40
use MARC::File::USMARC;
41
use MARC::File::XML;
42
43
if ( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
44
    MARC::File::XML->default_record_format('UNIMARC');
45
}
46
47
our($tagslib,$authorised_values_sth,$is_a_modif,$usedTagsLib,$mandatory_z3950);
48
49
our ($sec, $min, $hour, $mday, $mon, $year, undef, undef, undef) = localtime(time);
50
$year +=1900;
51
$mon +=1;
52
53
our %creators = (
54
    '000@' => sub { '     nam a22     7a 4500' },
55
    '005@' => sub { sprintf('%4d%02d%02d%02d%02d%02d.0', $year, $mon, $mday, $hour, $min, $sec) },
56
    '008@' => sub { substr($year,2,2) . sprintf("%02d%02d", $mon, $mday) . 't        xxu||||| |||| 00| 0 eng d' },
57
);
58
59
=item MARCfindbreeding
60
61
    $record = MARCfindbreeding($breedingid);
62
63
Look up the import record repository for the record with
64
record with id $breedingid.  If found, returns the decoded
65
MARC::Record; otherwise, -1 is returned (FIXME).
66
Returns as second parameter the character encoding.
67
68
=cut
69
70
sub MARCfindbreeding {
71
    my ( $id ) = @_;
72
    my ($marc, $encoding) = GetImportRecordMarc($id);
73
    # remove the - in isbn, koha store isbn without any -
74
    if ($marc) {
75
        my $record = MARC::Record->new_from_usmarc($marc);
76
        my ($isbnfield,$isbnsubfield) = GetMarcFromKohaField('biblioitems.isbn','');
77
        if ( $record->field($isbnfield) ) {
78
            foreach my $field ( $record->field($isbnfield) ) {
79
                foreach my $subfield ( $field->subfield($isbnsubfield) ) {
80
                    my $newisbn = $field->subfield($isbnsubfield);
81
                    $newisbn =~ s/-//g;
82
                    $field->update( $isbnsubfield => $newisbn );
83
                }
84
            }
85
        }
86
        # fix the unimarc 100 coded field (with unicode information)
87
        if (C4::Context->preference('marcflavour') eq 'UNIMARC' && $record->subfield(100,'a')) {
88
            my $f100a=$record->subfield(100,'a');
89
            my $f100 = $record->field(100);
90
            my $f100temp = $f100->as_string;
91
            $record->delete_field($f100);
92
            if ( length($f100temp) > 28 ) {
93
                substr( $f100temp, 26, 2, "50" );
94
                $f100->update( 'a' => $f100temp );
95
                my $f100 = MARC::Field->new( '100', '', '', 'a' => $f100temp );
96
                $record->insert_fields_ordered($f100);
97
            }
98
        }
99
100
        if ( !defined(ref($record)) ) {
101
            return -1;
102
        }
103
        else {
104
            # normalize author : probably UNIMARC specific...
105
            if (    C4::Context->preference("z3950NormalizeAuthor")
106
                and C4::Context->preference("z3950AuthorAuthFields") )
107
            {
108
                my ( $tag, $subfield ) = GetMarcFromKohaField("biblio.author");
109
110
 #                 my $summary = C4::Context->preference("z3950authortemplate");
111
                my $auth_fields =
112
                  C4::Context->preference("z3950AuthorAuthFields");
113
                my @auth_fields = split /,/, $auth_fields;
114
                my $field;
115
116
                if ( $record->field($tag) ) {
117
                    foreach my $tmpfield ( $record->field($tag)->subfields ) {
118
119
       #                        foreach my $subfieldcode ($tmpfield->subfields){
120
                        my $subfieldcode  = shift @$tmpfield;
121
                        my $subfieldvalue = shift @$tmpfield;
122
                        if ($field) {
123
                            $field->add_subfields(
124
                                "$subfieldcode" => $subfieldvalue )
125
                              if ( $subfieldcode ne $subfield );
126
                        }
127
                        else {
128
                            $field =
129
                              MARC::Field->new( $tag, "", "",
130
                                $subfieldcode => $subfieldvalue )
131
                              if ( $subfieldcode ne $subfield );
132
                        }
133
                    }
134
                }
135
                $record->delete_field( $record->field($tag) );
136
                foreach my $fieldtag (@auth_fields) {
137
                    next unless ( $record->field($fieldtag) );
138
                    my $lastname  = $record->field($fieldtag)->subfield('a');
139
                    my $firstname = $record->field($fieldtag)->subfield('b');
140
                    my $title     = $record->field($fieldtag)->subfield('c');
141
                    my $number    = $record->field($fieldtag)->subfield('d');
142
                    if ($title) {
143
144
#                         $field->add_subfields("$subfield"=>"[ ".ucfirst($title).ucfirst($firstname)." ".$number." ]");
145
                        $field->add_subfields(
146
                                "$subfield" => ucfirst($title) . " "
147
                              . ucfirst($firstname) . " "
148
                              . $number );
149
                    }
150
                    else {
151
152
#                       $field->add_subfields("$subfield"=>"[ ".ucfirst($firstname).", ".ucfirst($lastname)." ]");
153
                        $field->add_subfields(
154
                            "$subfield" => ucfirst($firstname) . ", "
155
                              . ucfirst($lastname) );
156
                    }
157
                }
158
                $record->insert_fields_ordered($field);
159
            }
160
            return $record, $encoding;
161
        }
162
    }
163
    return -1;
164
}
165
166
# Borrowed from MARC::Record::JSON, due to its lack of availability on CPAN
167
168
sub MARC::Record::as_json_record_structure {
169
    my $self = shift;
170
    my $data = { leader => $self->leader };
171
    my @fields;
172
    foreach my $field ($self->fields) {
173
        my $json_field = { tag => $field->tag };
174
175
        if ($field->is_control_field) {
176
            $json_field->{contents} = $field->data;
177
        } else {
178
            $json_field->{indicator1} = $field->indicator(1);
179
            $json_field->{indicator2} = $field->indicator(2);
180
181
            $json_field->{subfields} = [ $field->subfields ];
182
        }
183
184
        push @fields, $json_field;
185
    }
186
187
    $data->{fields} = \@fields;
188
189
    return $data;
190
}
191
192
=item GetMandatoryFieldZ3950
193
194
    This function return an hashref which containts all mandatory field
195
    to search with z3950 server.
196
197
=cut
198
199
sub GetMandatoryFieldZ3950($){
200
    my $frameworkcode = shift;
201
    my @isbn   = GetMarcFromKohaField('biblioitems.isbn',$frameworkcode);
202
    my @title  = GetMarcFromKohaField('biblio.title',$frameworkcode);
203
    my @author = GetMarcFromKohaField('biblio.author',$frameworkcode);
204
    my @issn   = GetMarcFromKohaField('biblioitems.issn',$frameworkcode);
205
    my @lccn   = GetMarcFromKohaField('biblioitems.lccn',$frameworkcode);
206
207
    return {
208
        $isbn[0].$isbn[1]     => 'isbn',
209
        $title[0].$title[1]   => 'title',
210
        $author[0].$author[1] => 'author',
211
        $issn[0].$issn[1]     => 'issn',
212
        $lccn[0].$lccn[1]     => 'lccn',
213
    };
214
}
215
216
sub build_tabs ($$$$$) {
217
    my($template, $record, $dbh,$encoding, $input) = @_;
218
    # fill arrays
219
    my @loop_data =();
220
    my $tag;
221
    my $i=0;
222
    my $authorised_values_sth = $dbh->prepare("select authorised_value,lib
223
        from authorised_values
224
        where category=? order by lib");
225
226
    # in this array, we will push all the 10 tabs
227
    # to avoid having 10 tabs in the template : they will all be in the same BIG_LOOP
228
    my @BIG_LOOP;
229
    my @HIDDEN_LOOP;
230
231
# loop through each tab 0 through 9
232
    foreach my $tag (sort(keys (%{$tagslib}))) {
233
        my $taglib = $tagslib->{$tag};
234
        my $indicator;
235
# if MARC::Record is not empty => use it as master loop, then add missing subfields that should be in the tab.
236
# if MARC::Record is empty => use tab as master loop.
237
        if ($record ne -1 && ($record->field($tag) || $tag eq '000')) {
238
            my @fields;
239
            if ($tag ne '000') {
240
                @fields = $record->field($tag);
241
            } else {
242
                push @fields,$record->leader();
243
            }
244
            foreach my $field (@fields)  {
245
                my $tag_writeout = "$tag ";
246
                $tag_writeout .= ($field->indicator(1) eq ' ' ? '_' : $field->indicator(1)) . ($field->indicator(1) eq ' ' ? '_' : $field->indicator(1)) . ' ' if ($tag>=10);
247
                my $tag_index = int(rand(1000000));
248
                my @subfields_data;
249
                if ($tag<10) {
250
                    my ($value,$subfield);
251
                    if ($tag ne '000') {
252
                        $value=$field->data();
253
                        $subfield="@";
254
                    } else {
255
                        $value = $field;
256
                        $subfield='@';
257
                    }
258
                    my $subfieldlib = $taglib->{$subfield};
259
                    next if ($subfieldlib->{kohafield} eq 'biblio.biblionumber');
260
261
                    push(@subfields_data, "$value");
262
                    $i++;
263
                } else {
264
                    my @subfields=$field->subfields();
265
                    foreach my $subfieldcount (0..$#subfields) {
266
                        my $subfield=$subfields[$subfieldcount][0];
267
                        my $value=$subfields[$subfieldcount][1];
268
                        my $subfieldlib = $taglib->{$subfield};
269
                        next if (length $subfield !=1);
270
                        next if ($subfieldlib->{tab} > 9 or $subfieldlib->{tab} == -1);
271
                        push(@subfields_data, "\$$subfield $value");
272
                        $i++;
273
                    }
274
                }
275
# now, loop again to add parameter subfield that are not in the MARC::Record
276
                foreach my $subfield (sort( keys %{$tagslib->{$tag}})) {
277
                    my $subfieldlib = $taglib->{$subfield};
278
                    next if (length $subfield !=1);
279
                    next if ($tag<10);
280
                    next if (!$subfieldlib->{mandatory});
281
                    next if ($subfieldlib->{tab} > 9 or $subfieldlib->{tab} == -1);
282
                    next if (defined($field->subfield($subfield)));
283
                    push(@subfields_data, "\$$subfield ");
284
                    $i++;
285
                }
286
                if (@subfields_data) {
287
                    $tag_writeout .= join(' ', @subfields_data);
288
                    push (@BIG_LOOP, $tag_writeout);
289
                }
290
# If there is more than 1 field, add an empty hidden field as separator.
291
            }
292
# if breeding is empty
293
        } else {
294
            my $tag_writeout = "$tag ";
295
            $tag_writeout .= '__ ' if ($tag>=10);
296
            my @subfields_data;
297
            foreach my $subfield (sort(keys %{$tagslib->{$tag}})) {
298
                my $subfieldlib = $taglib->{$subfield};
299
                next if (length $subfield !=1);
300
                next if (!$subfieldlib->{mandatory});
301
                next if ($subfieldlib->{tab} > 9);
302
303
                if (ref($creators{$tag . $subfield}) eq 'CODE') {
304
                    if (($subfieldlib->{hidden} <= -4) or ($subfieldlib->{hidden}>=5) or ($taglib->{tab} == -1)) {
305
                        my %row = (
306
                            tag => $tag,
307
                            index => int(rand(1000000)),
308
                            index_subfield => int(rand(1000000)),
309
                            random => int(rand(1000000)),
310
                            subfield => ($subfield eq '@' ? '00' : $subfield),
311
                            subfield_value => $creators{$tag . $subfield}(),
312
                        );
313
                        push @HIDDEN_LOOP, \%row;
314
                        next;
315
                    } else {
316
                        push @subfields_data, $creators{$tag . $subfield}();
317
                        next;
318
                    }
319
                }
320
321
                if ($tag >= 10) {
322
                    push @subfields_data, "\$$subfield ";
323
                } else {
324
                    push @subfields_data, "";
325
                }
326
                $i++;
327
            }
328
            next if (!@subfields_data);
329
            push (@BIG_LOOP, $tag_writeout . join(' ', @subfields_data));
330
        }
331
    }
332
#         $template->param($tabloop."XX" =>\@loop_data);
333
    $template->param(
334
        BIG_LOOP => join("\n", @BIG_LOOP),
335
        HIDDEN_LOOP => \@HIDDEN_LOOP,
336
        record_length => $#BIG_LOOP,
337
    );
338
}
339
340
#
341
# sub that tries to find authorities linked to the biblio
342
# the sub :
343
#   - search in the authority DB for the same authid (in $9 of the biblio)
344
#   - search in the authority DB for the same 001 (in $3 of the biblio in UNIMARC)
345
#   - search in the authority DB for the same values (exactly) (in all subfields of the biblio)
346
# if the authority is found, the biblio is modified accordingly to be connected to the authority.
347
# if the authority is not found, it's added, and the biblio is then modified to be connected to the authority.
348
#
349
350
sub BiblioAddAuthorities{
351
  my ( $record, $frameworkcode ) = @_;
352
  my $dbh=C4::Context->dbh;
353
  my $query=$dbh->prepare(qq|
354
SELECT authtypecode,tagfield
355
FROM marc_subfield_structure
356
WHERE frameworkcode=?
357
AND (authtypecode IS NOT NULL AND authtypecode<>\"\")|);
358
# SELECT authtypecode,tagfield
359
# FROM marc_subfield_structure
360
# WHERE frameworkcode=?
361
# AND (authtypecode IS NOT NULL OR authtypecode<>\"\")|);
362
  $query->execute($frameworkcode);
363
  my ($countcreated,$countlinked);
364
  while (my $data=$query->fetchrow_hashref){
365
    foreach my $field ($record->field($data->{tagfield})){
366
      next if ($field->subfield('3')||$field->subfield('9'));
367
      # No authorities id in the tag.
368
      # Search if there is any authorities to link to.
369
      my $query='at='.$data->{authtypecode}.' ';
370
      map {$query.= ' and he,ext="'.$_->[1].'"' if ($_->[0]=~/[A-z]/)}  $field->subfields();
371
      my ($error, $results, $total_hits)=SimpleSearch( $query, undef, undef, [ "authorityserver" ] );
372
    # there is only 1 result
373
      if ( $error ) {
374
        warn "BIBLIOADDSAUTHORITIES: $error";
375
        return (0,0) ;
376
      }
377
      if ($results && scalar(@$results)==1) {
378
        my $marcrecord = MARC::File::USMARC::decode($results->[0]);
379
        $field->add_subfields('9'=>$marcrecord->field('001')->data);
380
        $countlinked++;
381
      } elsif (scalar(@$results)>1) {
382
   #More than One result
383
   #This can comes out of a lack of a subfield.
384
#         my $marcrecord = MARC::File::USMARC::decode($results->[0]);
385
#         $record->field($data->{tagfield})->add_subfields('9'=>$marcrecord->field('001')->data);
386
  $countlinked++;
387
      } else {
388
  #There are no results, build authority record, add it to Authorities, get authid and add it to 9
389
  ###NOTICE : This is only valid if a subfield is linked to one and only one authtypecode
390
  ###NOTICE : This can be a problem. We should also look into other types and rejected forms.
391
         my $authtypedata=GetAuthType($data->{authtypecode});
392
         next unless $authtypedata;
393
         my $marcrecordauth=MARC::Record->new();
394
         my $authfield=MARC::Field->new($authtypedata->{auth_tag_to_report},'','',"a"=>"".$field->subfield('a'));
395
         map { $authfield->add_subfields($_->[0]=>$_->[1]) if ($_->[0]=~/[A-z]/ && $_->[0] ne "a" )}  $field->subfields();
396
         $marcrecordauth->insert_fields_ordered($authfield);
397
398
         # bug 2317: ensure new authority knows it's using UTF-8; currently
399
         # only need to do this for MARC21, as MARC::Record->as_xml_record() handles
400
         # automatically for UNIMARC (by not transcoding)
401
         # FIXME: AddAuthority() instead should simply explicitly require that the MARC::Record
402
         # use UTF-8, but as of 2008-08-05, did not want to introduce that kind
403
         # of change to a core API just before the 3.0 release.
404
         if (C4::Context->preference('marcflavour') eq 'MARC21') {
405
            SetMarcUnicodeFlag($marcrecordauth, 'MARC21');
406
         }
407
408
#          warn "AUTH RECORD ADDED : ".$marcrecordauth->as_formatted;
409
410
         my $authid=AddAuthority($marcrecordauth,'',$data->{authtypecode});
411
         $countcreated++;
412
         $field->add_subfields('9'=>$authid);
413
      }
414
    }
415
  }
416
  return ($countlinked,$countcreated);
417
}
418
419
# ========================
420
#          MAIN
421
#=========================
422
my $input = new CGI;
423
my $error = $input->param('error');
424
my $biblionumber  = $input->param('biblionumber'); # if biblionumber exists, it's a modif, not a new biblio.
425
my $breedingid    = $input->param('breedingid');
426
my $z3950         = $input->param('z3950');
427
my $op            = $input->param('op');
428
my $mode          = $input->param('mode');
429
my $record_text   = $input->param('record');
430
my $frameworkcode = $input->param('frameworkcode');
431
my $dbh           = C4::Context->dbh;
432
433
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
434
    {
435
        template_name   => "cataloguing/addbiblio-text.tmpl",
436
        query           => $input,
437
        type            => "intranet",
438
        authnotrequired => 0,
439
        flagsrequired   => { editcatalogue => 1 },
440
    }
441
);
442
443
if (is_ajax() && $op eq 'try_parse') {
444
    my @params = $input->param();
445
    my $record = TransformHtmlToMarc( \@params , $input );
446
    my $response = new C4::Output::JSONStream;
447
448
    eval {
449
           $record = TransformTextToMarc( $record_text, existing_record => $record )
450
    };
451
    if ( $@ ) {
452
        chomp $@;
453
        $response->param( type => 'input', error => 'parse_failed', message => $@ );
454
455
        output_with_http_headers $input, $cookie, $response->output, 'json';
456
        exit;
457
    }
458
459
    $response->param( record => $record->as_json_record_structure );
460
461
    output_with_http_headers $input, $cookie, $response->output, 'json';
462
    exit;
463
}
464
465
$frameworkcode = &GetFrameworkCode($biblionumber)
466
  if ( $biblionumber and not($frameworkcode) );
467
468
$frameworkcode = '' if ( $frameworkcode eq 'Default' );
469
470
# Getting the list of all frameworks
471
# get framework list
472
my $frameworks = getframeworks;
473
my @frameworkcodeloop;
474
foreach my $thisframeworkcode ( keys %$frameworks ) {
475
    my %row = (
476
        value         => $thisframeworkcode,
477
        frameworktext => $frameworks->{$thisframeworkcode}->{'frameworktext'},
478
    );
479
    if ($frameworkcode eq $thisframeworkcode){
480
        $row{'selected'}="selected=\"selected\"";
481
        }
482
    push @frameworkcodeloop, \%row;
483
}
484
$template->param( frameworkcodeloop => \@frameworkcodeloop,
485
    breedingid => $breedingid );
486
487
# ++ Global
488
$tagslib         = &GetMarcStructure( 1, $frameworkcode );
489
$usedTagsLib     = &GetUsedMarcStructure( $frameworkcode );
490
$mandatory_z3950 = GetMandatoryFieldZ3950($frameworkcode);
491
# -- Global
492
493
my $record   = -1;
494
my $encoding = "";
495
my (
496
    $biblionumbertagfield,
497
    $biblionumbertagsubfield,
498
    $biblioitemnumtagfield,
499
    $biblioitemnumtagsubfield,
500
    $bibitem,
501
    $biblioitemnumber
502
);
503
504
if (($biblionumber) && !($breedingid)){
505
    $record = GetMarcBiblio($biblionumber);
506
}
507
if ($breedingid) {
508
    ( $record, $encoding ) = MARCfindbreeding( $breedingid ) ;
509
}
510
511
$is_a_modif = 0;
512
513
if ($biblionumber) {
514
    $is_a_modif = 1;
515
    $template->param( title => $record->title(), );
516
517
    # if it's a modif, retrieve bibli and biblioitem numbers for the future modification of old-DB.
518
    ( $biblionumbertagfield, $biblionumbertagsubfield ) =
519
    &GetMarcFromKohaField( "biblio.biblionumber", $frameworkcode );
520
    ( $biblioitemnumtagfield, $biblioitemnumtagsubfield ) =
521
    &GetMarcFromKohaField( "biblioitems.biblioitemnumber", $frameworkcode );
522
523
    # search biblioitems value
524
    my $sth =  $dbh->prepare("select biblioitemnumber from biblioitems where biblionumber=?");
525
    $sth->execute($biblionumber);
526
    ($biblioitemnumber) = $sth->fetchrow;
527
}
528
529
#-------------------------------------------------------------------------------------
530
if ( $op eq "addbiblio" ) {
531
#-------------------------------------------------------------------------------------
532
    # getting html input
533
    my @params = $input->param();
534
    $record = TransformHtmlToMarc( \@params , $input );
535
    eval {
536
           $record = TransformTextToMarc( $record_text, existing_record => $record )
537
    };
538
    # check for a duplicate
539
    my ($duplicatebiblionumber,$duplicatetitle) = FindDuplicate($record) if (!$is_a_modif);
540
    my $confirm_not_duplicate = $input->param('confirm_not_duplicate');
541
    # it is not a duplicate (determined either by Koha itself or by user checking it's not a duplicate)
542
    if ( !$duplicatebiblionumber or $confirm_not_duplicate ) {
543
        my $oldbibnum;
544
        my $oldbibitemnum;
545
        if (C4::Context->preference("BiblioAddsAuthorities")){
546
          my ($countlinked,$countcreated)=BiblioAddAuthorities($record,$frameworkcode);
547
        }
548
        if ( $is_a_modif ) {
549
            ModBiblioframework( $biblionumber, $frameworkcode );
550
            ModBiblio( $record, $biblionumber, $frameworkcode );
551
        }
552
        else {
553
            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
554
        }
555
556
        if ($mode ne "popup"){
557
            print $input->redirect(
558
                "/cgi-bin/koha/cataloguing/additem.pl?biblionumber=$biblionumber&frameworkcode=$frameworkcode"
559
            );
560
            exit;
561
        } else {
562
          $template->param(
563
            biblionumber => $biblionumber,
564
            done         =>1,
565
            popup        =>1
566
          );
567
          $template->param( title => $record->subfield('200',"a") ) if ($record ne "-1" && C4::Context->preference('marcflavour') =~/unimarc/i);
568
          $template->param( title => $record->title() ) if ($record ne "-1" && C4::Context->preference('marcflavour') eq "usmarc");
569
          $template->param(
570
            popup => $mode,
571
            itemtype => $frameworkcode,
572
          );
573
          output_html_with_http_headers $input, $cookie, $template->output;
574
          exit;
575
        }
576
    } else {
577
    # it may be a duplicate, warn the user and do nothing
578
        build_tabs ($template, $record, $dbh,$encoding,$input);
579
        $template->param(
580
            biblionumber             => $biblionumber,
581
            biblioitemnumber         => $biblioitemnumber,
582
            duplicatebiblionumber    => $duplicatebiblionumber,
583
            duplicatebibid           => $duplicatebiblionumber,
584
            duplicatetitle           => $duplicatetitle,
585
        );
586
    }
587
}
588
elsif ( $op eq "delete" ) {
589
590
    my $error = &DelBiblio($biblionumber);
591
    if ($error) {
592
        warn "ERROR when DELETING BIBLIO $biblionumber : $error";
593
        print "Content-Type: text/html\n\n<html><body><h1>ERROR when DELETING BIBLIO $biblionumber : $error</h1></body></html>";
594
    exit;
595
    }
596
597
    print $input->redirect('/cgi-bin/koha/catalogue/search.pl');
598
    exit;
599
600
} else {
601
   #----------------------------------------------------------------------------
602
   # If we're in a duplication case, we have to set to "" the biblionumber
603
   # as we'll save the biblio as a new one.
604
    if ( $op eq "duplicate" ) {
605
        $biblionumber = "";
606
    }
607
608
#FIXME: it's kind of silly to go from MARC::Record to MARC::File::XML and then back again just to fix the encoding
609
    eval {
610
        my $uxml = $record->as_xml;
611
        MARC::Record::default_record_format("UNIMARC")
612
          if ( C4::Context->preference("marcflavour") eq "UNIMARC" );
613
        my $urecord = MARC::Record::new_from_xml( $uxml, 'UTF-8' );
614
        $record = $urecord;
615
    };
616
    build_tabs( $template, $record, $dbh, $encoding,$input );
617
    $template->param(
618
        biblionumber             => $biblionumber,
619
        biblionumbertagfield        => $biblionumbertagfield,
620
        biblionumbertagsubfield     => $biblionumbertagsubfield,
621
        biblioitemnumtagfield    => $biblioitemnumtagfield,
622
        biblioitemnumtagsubfield => $biblioitemnumtagsubfield,
623
        biblioitemnumber         => $biblioitemnumber,
624
    );
625
}
626
627
$template->param( title => $record->title() ) if ( $record ne "-1" );
628
$template->param(
629
    popup => $mode,
630
    frameworkcode => $frameworkcode,
631
    itemtype => $frameworkcode,
632
    itemtypes => GetItemTypeList(),
633
);
634
635
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/cataloguing/addbiblio.pl (-1 / +5 lines)
Lines 823-829 AND (authtypecode IS NOT NULL AND authtypecode<>\"\")|); Link Here
823
823
824
# ========================
824
# ========================
825
#          MAIN
825
#          MAIN
826
#=========================
826
#========================
827
my $input = new CGI;
827
my $input = new CGI;
828
my $error = $input->param('error');
828
my $error = $input->param('error');
829
my $biblionumber  = $input->param('biblionumber'); # if biblionumber exists, it's a modif, not a new biblio.
829
my $biblionumber  = $input->param('biblionumber'); # if biblionumber exists, it's a modif, not a new biblio.
Lines 836-841 my $redirect = $input->param('redirect'); Link Here
836
my $dbh           = C4::Context->dbh;
836
my $dbh           = C4::Context->dbh;
837
837
838
my $userflags = ($frameworkcode eq 'FA') ? "fast_cataloging" : "edit_catalogue";
838
my $userflags = ($frameworkcode eq 'FA') ? "fast_cataloging" : "edit_catalogue";
839
if (C4::Context->preference('MARCEditor') eq 'text') {
840
	print $input->redirect('/cgi-bin/koha/cataloguing/addbiblio-text.pl?' . $ENV{'QUERY_STRING'});
841
	exit;
842
}
839
843
840
$frameworkcode = &GetFrameworkCode($biblionumber)
844
$frameworkcode = &GetFrameworkCode($biblionumber)
841
  if ( $biblionumber and not($frameworkcode) and $op ne 'addbiblio' );
845
  if ( $biblionumber and not($frameworkcode) and $op ne 'addbiblio' );
(-)a/cataloguing/framework-jsonp.pl (+60 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
use CGI;
4
use C4::Context;
5
use C4::Biblio;
6
7
my $input = new CGI;
8
our $dbh = C4::Context->dbh;
9
10
my $frameworkcode = $input->param('frameworkcode') || '';
11
my $info = $input->param('info') || 'kohalinks';
12
my $prepend = $input->param('prepend') || '';
13
my $append = $input->param('append') || '';
14
15
my $tagslib = GetMarcStructure(1, $frameworkcode);
16
17
print $input->header('text/javascript');
18
19
print $prepend . "{";
20
21
if ($info eq 'kohalinks') {
22
	foreach my $tag (sort(keys (%{$tagslib}))) {
23
		my $taglib = $tagslib->{$tag};
24
		foreach my $subfield (sort(keys %{$taglib})) {
25
			my $subfieldlib = $taglib->{$subfield};
26
			if ($subfieldlib->{kohafield}) {
27
				print "'" . $subfieldlib->{kohafield} . "':['$tag','$subfield'],";
28
			}
29
		}
30
	}
31
} elsif ($info eq 'mandatory') {
32
	my @mandatory_tags;
33
	my @mandatory_subfields;
34
35
	foreach my $tag (sort(keys (%{$tagslib}))) {
36
		my $taglib = $tagslib->{$tag};
37
		push @mandatory_tags, $tag if ($taglib->{mandatory});
38
		foreach my $subfield (sort(keys %{$taglib})) {
39
			my $subfieldlib = $taglib->{$subfield};
40
			push @mandatory_subfields, "['$tag','$subfield']" if ($subfieldlib->{mandatory} && $subfieldlib->{tab} != -1 && $subfieldlib->{tab} != 10);
41
		}
42
	}
43
44
	print "tags:[";
45
	foreach my $tag (@mandatory_tags) { print "'$tag',"; }
46
	print "],";
47
48
	print "subfields:[";
49
	foreach my $subfield (@mandatory_subfields) { print "$subfield,"; }
50
	print "]";
51
} elsif ($info eq 'itemtypes') {
52
	my $sth=$dbh->prepare("select itemtype,description from itemtypes order by description");
53
	$sth->execute;
54
55
	while (my ($itemtype,$description) = $sth->fetchrow_array) {
56
		print "'$itemtype':'$description',";
57
	}
58
}
59
60
print "}" . $append;
(-)a/installer/data/mysql/en/mandatory/sysprefs.sql (+1 lines)
Lines 317-319 INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ( Link Here
317
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice');
317
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('BasketConfirmations', '1', 'When closing or reopening a basket,', 'always ask for confirmation.|do not ask for confirmation.', 'Choice');
318
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn           | a|a     d', NULL, NULL, 'Textarea');
318
INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('MARCAuthorityControlField008', '|| aca||aabn           | a|a     d', NULL, NULL, 'Textarea');
319
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo');
319
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpenLibraryCovers',0,'If ON Openlibrary book covers will be show',NULL,'YesNo');
320
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('MARCEditor','normal','Use the normal or textual MARC editor','normal|text','Choice');
(-)a/installer/data/mysql/updatedatabase.pl (+7 lines)
Lines 4432-4437 if (C4::Context->preference("Version") < TransformToNum($DBversion)) { Link Here
4432
    SetVersion ($DBversion);
4432
    SetVersion ($DBversion);
4433
}
4433
}
4434
4434
4435
$DBversion = "3.05.00.XXX";
4436
if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
4437
    $dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('MARCEditor','normal','Use the normal or textual MARC editor','normal|text','Choice');");
4438
    print "Upgrade to $DBversion done (Add syspref MARCEditor)\n";
4439
    SetVersion($DBversion);
4440
}
4441
4435
=head1 FUNCTIONS
4442
=head1 FUNCTIONS
4436
4443
4437
=head2 DropAllForeignKeys($table)
4444
=head2 DropAllForeignKeys($table)
(-)a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css (+5 lines)
Lines 2100-2102 fieldset.rows+h3 {clear:both;padding-top:.5em;} Link Here
2100
    color : #cc0000;
2100
    color : #cc0000;
2101
    }
2101
    }
2102
2102
2103
#embedded_z3950 {
2104
	width: 100%;
2105
	height: 500px;
2106
	border: none;
2107
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc (+3 lines)
Lines 72-77 Link Here
72
<script type="text/javascript" src="[% yuipath %]/container/container_core-min.js"></script> 
72
<script type="text/javascript" src="[% yuipath %]/container/container_core-min.js"></script> 
73
<script type="text/javascript" src="[% yuipath %]/menu/menu-min.js"></script> 
73
<script type="text/javascript" src="[% yuipath %]/menu/menu-min.js"></script> 
74
74
75
<script type="text/javascript">
76
var koha = { themelang: '[% themelang %]' };
77
</script>
75
<!-- koha core js -->
78
<!-- koha core js -->
76
<script type="text/javascript" src="[% themelang %]/js/staff-global.js"></script>
79
<script type="text/javascript" src="[% themelang %]/js/staff-global.js"></script>
77
[% IF ( intranetuserjs ) %]
80
[% IF ( intranetuserjs ) %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/js/marc.js (+194 lines)
Line 0 Link Here
1
/* From MARC::Record::JSON: http://code.google.com/p/marc-json/downloads/list */
2
/* Modified by Jesse Weaver */
3
4
/*===========================================
5
  MARC.Field(fdata)
6
7
A MARC Field, as pulled from the json data.
8
9
You can usually get what you want using MARCRecord.subfield(tag, sub)
10
but may need this for more advanced usage
11
12
  f = new MARC.Field(data);
13
  isbn = f.subfield('a'); // if it's an 020, of course
14
  isbn = f.as_string('a'); // same thing
15
16
  alltitlestuff = f.as_string(); // if it's a 245
17
  propertitle = f.as_string('anp'); // just the a, n, and p subfields
18
19
  subfield('a', sep=' ') -- returns:
20
    '' iff there is no subfield a
21
    'value' iff there is exactly one subfield a
22
    'value1|value2' iff there are more than on subfield a's
23
24
  as_string(spec, sep, includesftags) -- where spec is either empty or a string of concat'd subfields.
25
    spec is either null (all subfields) or a string listing the subfields (e.g., 'a' or 'abh')
26
    sep is the string used to separate the values; a single space is the default
27
    includesftags is a boolean that determines if the subfield tags will be included (e.g, $$a data $$h moredata)
28
29
    It returns the found data joined by the string in 'sep', or an empty string if nothing is found.
30
31
32
===============================================*/
33
34
marc = {}
35
36
marc.field = function ( tag, ind1, ind2, subfields ) {
37
    this.tag = tag;
38
39
    if (tag < 10) {
40
        this.is_control_field = true;
41
        this.data = ind1;
42
        return;
43
    }
44
45
    this._subfields = subfields;
46
47
    this._subfield_map = {};
48
49
    if ( ind1 == '' ) ind1 = ' ';
50
    if ( ind2 == '' ) ind2 = ' ';
51
52
    this._indicators = [ ind1, ind2 ];
53
54
    var field = this;
55
56
    $.each( subfields, function( i, subfield ) {
57
        var code = subfield[0];
58
59
        if (!(code in field._subfield_map)) field._subfield_map[code] = [];
60
61
        field._subfield_map[code].push(subfield[1]);
62
    } );
63
}
64
65
$.extend( marc.field.prototype, {
66
    indicator: function(ind) {
67
        if (this.is_control_field) throw TypeError('indicator() called on control field');
68
        if (ind != 1 && ind != 2) return null;
69
70
        return this._indicators[ind - 1];
71
    },
72
73
    subfield: function(code) {
74
        if (this.is_control_field) throw TypeError('subfield() called on control field');
75
        if (!(code in this._subfield_map)) return null;
76
77
        return this._subfield_map[code][0];
78
    },
79
80
    subfields: function(code) {
81
        if (this.is_control_field) throw TypeError('subfields() called on control field');
82
        if (code === undefined) {
83
            return self._subfields;
84
        } else {
85
            if (!(code in this._subfield_map)) return null;
86
87
            return this._subfield_map[code];
88
        }
89
    },
90
91
    as_string: function() {
92
        var buffer = [ this.tag, ' ' ];
93
94
        if ( this.is_control_field ) {
95
            buffer.push( this.data );
96
        } else {
97
            buffer.push( this._indicators[0], this._indicators[1], ' ' );
98
99
            $.each( this.subfields, function( i, subfield ) {
100
                buffer.push( '$', subfield[0], ' ', subfield[1] );
101
            } );
102
        }
103
    },
104
});
105
106
107
/*===========================================
108
MARCRecord -- a MARC::Record-like object
109
110
  r.cfield('008') -- the contents of the 008 control field
111
  r.cfield('LDR') -- ditto with the leader
112
113
  array = r.controlFieldTags(); -- a list of the control field tags, for feeding into cfield
114
115
  array = r.dfield('022') -- all the ISSN fields
116
  r.dfield('022')[0].as_string -- the first 022 as a string
117
  r.dfield('245')[0].as_string(); -- the title as a string
118
  r.dfield('FAK') -- returns an empty array
119
120
  r.dfields() -- return an array of all dfields
121
122
  r.field('245')[0] -- 'field' is an alias for 'dfield'
123
124
  r.subfield('245', 'a') -- the first 245/a
125
  r.subfield('100', 'a') -- the author?
126
127
  // Convenience functions
128
129
  str = r.title();
130
  str = r.author(); // Looks in 100, 110, and 111 in that order; returns '' on fail
131
  edition = r.edition(); // from the 250/a
132
133
134
===========================================*/
135
136
marc.record = function(structure) {
137
    this.leader = new Array(25).join(' '); // Poor man's ' ' x 24
138
    this._fields = [];
139
    this._field_map = {};
140
141
    if (structure) {
142
        this.leader = structure.leader;
143
        var record = this;
144
145
        $.each( structure.fields, function( i, field ) {
146
            var tag = field.tag;
147
148
            if ( !( tag in record._field_map ) ) record._field_map[tag] = [];
149
150
            var f = field.contents ? new marc.field( tag, field.contents ) : new marc.field( tag, field.indicator1, field.indicator2, field.subfields );
151
152
            record._fields.push( f );
153
            record._field_map[tag].push( f );
154
        } );
155
    }
156
}
157
158
$.extend( marc.record.prototype, {
159
    subfield: function(tag, subfield) {
160
        if ( !( tag in this._field_map ) ) return false;
161
162
        if ( subfield === undefined ) return true;
163
164
        var found = null;
165
166
        $.each( this._field_map[tag], function( i, field ) {
167
            found = field.subfield( subfield );
168
169
            if ( found ) return false;
170
        } );
171
172
        return found;
173
    },
174
175
    has: function( tag, subfield ) {
176
        return Boolean( this.subfield( tag, subfield ) );
177
    },
178
179
    field: function(tag) {
180
        if (!(tag in this._field_map)) return null;
181
182
        return this._field_map[tag][0];
183
    },
184
185
    fields: function(tag) {
186
        if (tag === undefined) {
187
            return self._fields;
188
        } else {
189
            if (!(tag in this._field_map)) return null;
190
191
            return this._field_map[tag];
192
        }
193
    },
194
} );
(-)a/koha-tmpl/intranet-tmpl/prog/en/js/pages/addbiblio-text.js (+82 lines)
Line 0 Link Here
1
addbiblio = {};
2
3
$.extend( addbiblio, {
4
	submit: function() {
5
		$.ajax( {
6
			url: '/cgi-bin/koha/cataloguing/addbiblio-text.pl',
7
			type: 'POST',
8
			dataType: 'json',
9
			data: $( '#f input[name^="tag"]' ).serialize() + '&op=try_parse&record=' + escape(addbiblio.editor.getCode()),
10
			success: addbiblio.submit.finished,
11
		} );
12
	},
13
	insert_itemtype: function( event ) {
14
		var iter = addbiblio.editor.cursorPosition();
15
		addbiblio.editor.insertIntoLine( iter.line, iter.character, $( '#itemtypes' ).val() );
16
17
		return false;
18
	},
19
	z3950_search: function() {
20
		window.open( "/cgi-bin/koha/cataloguing/z3950_search.pl?biblionumber=" + addbiblio.biblionumber,"z3950search",'width=740,height=450,location=yes,toolbar=no,scrollbars=yes,resize=yes' );
21
	},
22
	not_duplicate: function() {
23
		$( "#confirm_not_duplicate" ).attr( "value", "1" );
24
		$( "#f" ).get( 0 ).submit();
25
	},
26
} );
27
28
$.extend( addbiblio.submit, {
29
	finished: function( data, status_ ) {
30
		if ( data.error ) {
31
			humanMsg.displayMsg( '<strong>Watch your language:</strong> ' + data.message );
32
			return false;
33
		}
34
35
		var record = new marc.record(data.record);
36
37
		var missing_tags = [], missing_subfields = [];
38
39
		$.each( addbiblio.mandatory.tags, function( i, tag ) {
40
			if ( tag == '000' ) {
41
				if ( !record.leader) missing_tags.push( 'leader' );
42
			} else if ( !record.has( tag ) ) {
43
				missing_tags.push( tag );
44
			}
45
		} );
46
47
		$.each( addbiblio.mandatory.subfields, function( i, sf ) {
48
			if ( sf[0].substring( 0, 2 ) != '00' && !record.has( sf[0], sf[1] ) ) {
49
				missing_subfields.push( sf.join( '$' ) );
50
			}
51
		} );
52
53
		if ( missing_tags.length || missing_subfields.length ) {
54
			message = [];
55
56
			if ( missing_tags.length ) {
57
				message.push( missing_tags.join( ', ' ) + ' tags' );
58
			}
59
60
			if ( missing_subfields.length ) {
61
				message.push( missing_subfields.join( ', ' ) + ' subfields' );
62
			}
63
64
			humanMsg.displayMsg( '<strong>Record is missing pieces:</strong> ' + message.join( ' and ' ) + ' are mandatory' );
65
			return;
66
		}
67
68
		$( '#f' ).get( 0 ).submit();
69
	}
70
} );
71
72
$( function () {
73
	$( '#insert-itemtype' ).click( addbiblio.insert_itemtype );
74
75
	addbiblio.editor = CodeMirror.fromTextArea('record', {
76
		height: "350px",
77
		parserfile: "parsemarc.js",
78
		stylesheet: koha.themelang + "/lib/codemirror/css/marccolors.css",
79
		path: koha.themelang + "/lib/codemirror/js/",
80
		autoMatchParens: true
81
	});
82
} );
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/LICENSE (+23 lines)
Line 0 Link Here
1
 Copyright (c) 2007-2008 Marijn Haverbeke
2
3
 This software is provided 'as-is', without any express or implied
4
 warranty. In no event will the authors be held liable for any
5
 damages arising from the use of this software.
6
7
 Permission is granted to anyone to use this software for any
8
 purpose, including commercial applications, and to alter it and
9
 redistribute it freely, subject to the following restrictions:
10
11
 1. The origin of this software must not be misrepresented; you must
12
    not claim that you wrote the original software. If you use this
13
    software in a product, an acknowledgment in the product
14
    documentation would be appreciated but is not required.
15
16
 2. Altered source versions must be plainly marked as such, and must
17
    not be misrepresented as being the original software.
18
19
 3. This notice may not be removed or altered from any source
20
    distribution.
21
22
 Marijn Haverbeke
23
 marijnh at gmail
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/csscolors.css (+47 lines)
Line 0 Link Here
1
.editbox {
2
  margin: .4em;
3
  padding: 0;
4
  font-family: monospace;
5
  font-size: 10pt;
6
  color: black;
7
}
8
9
pre.code, .editbox {
10
  color: #666666;
11
}
12
13
.editbox p {
14
  margin: 0;
15
}
16
17
span.css-at {
18
  color: #770088;
19
}
20
21
span.css-unit {
22
  color: #228811;
23
}
24
25
span.css-value {
26
  color: #770088;
27
}
28
29
span.css-identifier {
30
  color: black;
31
}
32
33
span.css-important {
34
  color: #0000FF;
35
}
36
37
span.css-colorcode {
38
  color: #004499;
39
}
40
41
span.css-comment {
42
  color: #AA7700;
43
}
44
45
span.css-string {
46
  color: #AA2222;
47
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/docs.css (+42 lines)
Line 0 Link Here
1
body {
2
  margin: 0;
3
  font-family: tahoma, arial, sans-serif;
4
  padding: 3em 6em;
5
  color: black;
6
}
7
8
h1 {
9
  font-size: 22pt;
10
}
11
12
h2 {
13
  font-size: 14pt;
14
}
15
16
p.rel {
17
  padding-left: 2em;
18
  text-indent: -2em;
19
}
20
21
div.border {
22
  border: 1px solid black;
23
  padding: 3px;
24
}
25
26
code {
27
  font-family: courier, monospace;
28
  font-size: 90%;
29
  color: #155;
30
}
31
32
pre.code {
33
  margin: 1.1em 12px;
34
  border: 1px solid #CCCCCC;
35
  color: black;
36
  padding: .4em;
37
  font-family: courier, monospace;
38
}
39
40
.warn {
41
  color: #C00;
42
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/jscolors.css (+55 lines)
Line 0 Link Here
1
.editbox {
2
  margin: .4em;
3
  padding: 0;
4
  font-family: monospace;
5
  font-size: 10pt;
6
  color: black;
7
}
8
9
pre.code, .editbox {
10
  color: #666666;
11
}
12
13
.editbox p {
14
  margin: 0;
15
}
16
17
span.js-punctuation {
18
  color: #666666;
19
}
20
21
span.js-operator {
22
  color: #666666;
23
}
24
25
span.js-keyword {
26
  color: #770088;
27
}
28
29
span.js-atom {
30
  color: #228811;
31
}
32
33
span.js-variable {
34
  color: black;
35
}
36
37
span.js-variabledef {
38
  color: #0000FF;
39
}
40
41
span.js-localvariable {
42
  color: #004499;
43
}
44
45
span.js-property {
46
  color: black;
47
}
48
49
span.js-comment {
50
  color: #AA7700;
51
}
52
53
span.js-string {
54
  color: #AA2222;
55
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/marccolors.css (+24 lines)
Line 0 Link Here
1
2
.editbox {
3
  margin: .4em;
4
  padding: 0;
5
  font-family: monospace;
6
  font-size: 10pt;
7
  color: black;
8
}
9
10
.editbox p {
11
  margin: 0;
12
}
13
14
span.marc-tag {
15
  color: #880;
16
}
17
18
span.marc-indicator {
19
  color: #088;
20
}
21
22
span.marc-subfield {
23
  color: #808;
24
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/sparqlcolors.css (+39 lines)
Line 0 Link Here
1
.editbox {
2
  margin: .4em;
3
  padding: 0;
4
  font-family: monospace;
5
  font-size: 10pt;
6
  color: black;
7
}
8
9
.editbox p {
10
  margin: 0;
11
}
12
13
span.sp-keyword {
14
  color: #708;
15
}
16
17
span.sp-prefixed {
18
  color: #5d1;
19
}
20
21
span.sp-var {
22
  color: #00c;
23
}
24
25
span.sp-comment {
26
  color: #a70;
27
}
28
29
span.sp-literal {
30
  color: #a22;
31
}
32
33
span.sp-uri {
34
  color: #292;
35
}
36
37
span.sp-operator {
38
  color: #088;
39
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/css/xmlcolors.css (+51 lines)
Line 0 Link Here
1
.editbox {
2
  margin: .4em;
3
  padding: 0;
4
  font-family: monospace;
5
  font-size: 10pt;
6
  color: black;
7
}
8
9
.editbox p {
10
  margin: 0;
11
}
12
13
span.xml-tagname {
14
  color: #A0B;
15
}
16
17
span.xml-attribute {
18
  color: #281;
19
}
20
21
span.xml-punctuation {
22
  color: black;
23
}
24
25
span.xml-attname {
26
  color: #00F;
27
}
28
29
span.xml-comment {
30
  color: #A70;
31
}
32
33
span.xml-cdata {
34
  color: #48A;
35
}
36
37
span.xml-processing {
38
  color: #999;
39
}
40
41
span.xml-entity {
42
  color: #A22;
43
}
44
45
span.xml-error {
46
  color: #F00;
47
}
48
49
span.xml-text {
50
  color: black;
51
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/codemirror.js (+219 lines)
Line 0 Link Here
1
/* CodeMirror main module
2
 *
3
 * Implements the CodeMirror constructor and prototype, which take care
4
 * of initializing the editor frame, and providing the outside interface.
5
 */
6
7
// The CodeMirrorConfig object is used to specify a default
8
// configuration. If you specify such an object before loading this
9
// file, the values you put into it will override the defaults given
10
// below. You can also assign to it after loading.
11
var CodeMirrorConfig = window.CodeMirrorConfig || {};
12
13
var CodeMirror = (function(){
14
  function setDefaults(object, defaults) {
15
    for (var option in defaults) {
16
      if (!object.hasOwnProperty(option))
17
        object[option] = defaults[option];
18
    }
19
  }
20
  function forEach(array, action) {
21
    for (var i = 0; i < array.length; i++)
22
      action(array[i]);
23
  }
24
25
  // These default options can be overridden by passing a set of
26
  // options to a specific CodeMirror constructor. See manual.html for
27
  // their meaning.
28
  setDefaults(CodeMirrorConfig, {
29
    stylesheet: "",
30
    path: "",
31
    parserfile: [],
32
    basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"],
33
    linesPerPass: 15,
34
    passDelay: 200,
35
    continuousScanning: false,
36
    saveFunction: null,
37
    onChange: null,
38
    undoDepth: 20,
39
    undoDelay: 800,
40
    disableSpellcheck: true,
41
    textWrapping: true,
42
    readOnly: false,
43
    width: "100%",
44
    height: "300px",
45
    autoMatchParens: false,
46
    parserConfig: null,
47
    dumbTabs: false,
48
    activeTokens: null,
49
    cursorActivity: null
50
  });
51
52
  function CodeMirror(place, options) {
53
    // Use passed options, if any, to override defaults.
54
    this.options = options = options || {};
55
    setDefaults(options, CodeMirrorConfig);
56
57
    var frame = this.frame = document.createElement("IFRAME");
58
    frame.src = "javascript:false;";
59
    frame.style.border = "0";
60
    frame.style.width = options.width;
61
    frame.style.height = options.height;
62
    // display: block occasionally suppresses some Firefox bugs, so we
63
    // always add it, redundant as it sounds.
64
    frame.style.display = "block";
65
66
    if (place.appendChild)
67
      place.appendChild(frame);
68
    else
69
      place(frame);
70
71
    // Link back to this object, so that the editor can fetch options
72
    // and add a reference to itself.
73
    frame.CodeMirror = this;
74
    this.win = frame.contentWindow;
75
76
    if (typeof options.parserfile == "string")
77
      options.parserfile = [options.parserfile];
78
    if (typeof options.stylesheet == "string")
79
      options.stylesheet = [options.stylesheet];
80
81
    var html = ["<html><head>"];
82
    forEach(options.stylesheet, function(file) {
83
      html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + "\"/>");
84
    });
85
    forEach(options.basefiles.concat(options.parserfile), function(file) {
86
      html.push("<script type=\"text/javascript\" src=\"" + options.path + file + "\"></script>");
87
    });
88
    html.push("</head><body style=\"border-width: 0;\" class=\"editbox\" spellcheck=\"" +
89
              (options.disableSpellcheck ? "false" : "true") + "\"></body></html>");
90
91
    var doc = this.win.document;
92
    doc.open();
93
    doc.write(html.join(""));
94
    doc.close();
95
  }
96
97
  CodeMirror.prototype = {
98
    getCode: function() {return this.editor.getCode();},
99
    setCode: function(code) {this.editor.importCode(code);},
100
    selection: function() {return this.editor.selectedText();},
101
    reindent: function() {this.editor.reindent();},
102
103
    focus: function() {
104
      this.win.focus();
105
      if (this.editor.selectionSnapshot) // IE hack
106
        this.win.select.selectCoords(this.win, this.editor.selectionSnapshot);
107
    },
108
    replaceSelection: function(text) {
109
      this.focus();
110
      this.editor.replaceSelection(text);
111
      return true;
112
    },
113
    replaceChars: function(text, start, end) {
114
      this.editor.replaceChars(text, start, end);
115
    },
116
    getSearchCursor: function(string, fromCursor) {
117
      return this.editor.getSearchCursor(string, fromCursor);
118
    },
119
120
    cursorPosition: function(start) {
121
      if (this.win.select.ie_selection) this.focus();
122
      return this.editor.cursorPosition(start);
123
    },
124
    firstLine: function() {return this.editor.firstLine();},
125
    lastLine: function() {return this.editor.lastLine();},
126
    nextLine: function(line) {return this.editor.nextLine(line);},
127
    prevLine: function(line) {return this.editor.prevLine(line);},
128
    lineContent: function(line) {return this.editor.lineContent(line);},
129
    setLineContent: function(line, content) {this.editor.setLineContent(line, content);},
130
    insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);},
131
    selectLines: function(startLine, startOffset, endLine, endOffset) {
132
      this.win.focus();
133
      this.editor.selectLines(startLine, startOffset, endLine, endOffset);
134
    },
135
    nthLine: function(n) {
136
      var line = this.firstLine();
137
      for (; n > 1 && line !== false; n--)
138
        line = this.nextLine(line);
139
      return line;
140
    },
141
    lineNumber: function(line) {
142
      var num = 0;
143
      while (line !== false) {
144
        num++;
145
        line = this.prevLine(line);
146
      }
147
      return num;
148
    },
149
150
    // Old number-based line interface
151
    jumpToLine: function(n) {
152
      this.selectLines(this.nthLine(n), 0);
153
      this.win.focus();
154
    },
155
    currentLine: function() {
156
      return this.lineNumber(this.cursorPosition().line);
157
    }
158
  };
159
160
  CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}};
161
162
  CodeMirror.replace = function(element) {
163
    if (typeof element == "string")
164
      element = document.getElementById(element);
165
    return function(newElement) {
166
      element.parentNode.replaceChild(newElement, element);
167
    };
168
  };
169
170
  CodeMirror.fromTextArea = function(area, options) {
171
    if (typeof area == "string")
172
      area = document.getElementById(area);
173
174
    options = options || {};
175
    if (area.style.width) options.width = area.style.width;
176
    if (area.style.height) options.height = area.style.height;
177
    if (options.content == null) options.content = area.value;
178
179
    if (area.form) {
180
      function updateField() {
181
        area.value = mirror.getCode();
182
      }
183
      if (typeof area.form.addEventListener == "function")
184
        area.form.addEventListener("submit", updateField, false);
185
      else
186
        area.form.attachEvent("onsubmit", updateField);
187
    }
188
189
    function insert(frame) {
190
      if (area.nextSibling)
191
        area.parentNode.insertBefore(frame, area.nextSibling);
192
      else
193
        area.parentNode.appendChild(frame);
194
    }
195
196
    area.style.display = "none";
197
    var mirror = new CodeMirror(insert, options);
198
    return mirror;
199
  };
200
201
  CodeMirror.isProbablySupported = function() {
202
    // This is rather awful, but can be useful.
203
    var match;
204
    if (window.opera)
205
      return Number(window.opera.version()) >= 9.52;
206
    else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./)))
207
      return Number(match[1]) >= 3;
208
    else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/)))
209
      return Number(match[1]) >= 6;
210
    else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i))
211
      return Number(match[1]) >= 20050901;
212
    else if (/Chrome\//.test(navigator.userAgent))
213
      return true;
214
    else
215
      return null;
216
  };
217
218
  return CodeMirror;
219
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/editor.js (+1176 lines)
Line 0 Link Here
1
/* The Editor object manages the content of the editable frame. It
2
 * catches events, colours nodes, and indents lines. This file also
3
 * holds some functions for transforming arbitrary DOM structures into
4
 * plain sequences of <span> and <br> elements
5
 */
6
7
var safeWhiteSpace, splitSpaces;
8
function setWhiteSpaceModel(collapsing) {
9
  safeWhiteSpace = collapsing ?
10
    // Make sure a string does not contain two consecutive 'collapseable'
11
    // whitespace characters.
12
    function(n) {
13
      var buffer = [], nb = true;
14
      for (; n > 0; n--) {
15
        buffer.push((nb || n == 1) ? nbsp : " ");
16
        nb = !nb;
17
      }
18
      return buffer.join("");
19
    } :
20
    function(n) {
21
      var buffer = [];
22
      for (; n > 0; n--) buffer.push(" ");
23
      return buffer.join("");
24
    };
25
  splitSpaces = collapsing ?
26
    // Create a set of white-space characters that will not be collapsed
27
    // by the browser, but will not break text-wrapping either.
28
    function(string) {
29
      if (string.charAt(0) == " ") string = nbsp + string.slice(1);
30
      return string.replace(/[\t \u00a0]{2,}/g, function(s) {return safeWhiteSpace(s.length);});
31
    } :
32
    function(string) {return string;};
33
}
34
35
function makePartSpan(value, doc) {
36
  var text = value;
37
  if (value.nodeType == 3) text = value.nodeValue;
38
  else value = doc.createTextNode(text);
39
40
  var span = doc.createElement("SPAN");
41
  span.isPart = true;
42
  span.appendChild(value);
43
  span.currentText = text;
44
  return span;
45
}
46
47
var Editor = (function(){
48
  // The HTML elements whose content should be suffixed by a newline
49
  // when converting them to flat text.
50
  var newlineElements = {"P": true, "DIV": true, "LI": true};
51
52
  function asEditorLines(string) {
53
    return splitSpaces(string.replace(/\t/g, "  ").replace(/\u00a0/g, " ")).replace(/\r\n?/g, "\n").split("\n");
54
  }
55
56
  var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
57
58
  // Helper function for traverseDOM. Flattens an arbitrary DOM node
59
  // into an array of textnodes and <br> tags.
60
  function simplifyDOM(root) {
61
    var doc = root.ownerDocument;
62
    var result = [];
63
    var leaving = false;
64
65
    function simplifyNode(node) {
66
      if (node.nodeType == 3) {
67
        var text = node.nodeValue = splitSpaces(node.nodeValue.replace(/[\n\r]/g, ""));
68
        if (text.length) leaving = false;
69
        result.push(node);
70
      }
71
      else if (node.nodeName == "BR" && node.childNodes.length == 0) {
72
        leaving = true;
73
        result.push(node);
74
      }
75
      else {
76
        forEach(node.childNodes, simplifyNode);
77
        if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) {
78
          leaving = true;
79
          result.push(doc.createElement("BR"));
80
        }
81
      }
82
    }
83
84
    simplifyNode(root);
85
    return result;
86
  }
87
88
  // Creates a MochiKit-style iterator that goes over a series of DOM
89
  // nodes. The values it yields are strings, the textual content of
90
  // the nodes. It makes sure that all nodes up to and including the
91
  // one whose text is being yielded have been 'normalized' to be just
92
  // <span> and <br> elements.
93
  // See the story.html file for some short remarks about the use of
94
  // continuation-passing style in this iterator.
95
  function traverseDOM(start){
96
    function yield(value, c){cc = c; return value;}
97
    function push(fun, arg, c){return function(){return fun(arg, c);};}
98
    function stop(){cc = stop; throw StopIteration;};
99
    var cc = push(scanNode, start, stop);
100
    var owner = start.ownerDocument;
101
    var nodeQueue = [];
102
103
    // Create a function that can be used to insert nodes after the
104
    // one given as argument.
105
    function pointAt(node){
106
      var parent = node.parentNode;
107
      var next = node.nextSibling;
108
      return function(newnode) {
109
        parent.insertBefore(newnode, next);
110
      };
111
    }
112
    var point = null;
113
114
    // Insert a normalized node at the current point. If it is a text
115
    // node, wrap it in a <span>, and give that span a currentText
116
    // property -- this is used to cache the nodeValue, because
117
    // directly accessing nodeValue is horribly slow on some browsers.
118
    // The dirty property is used by the highlighter to determine
119
    // which parts of the document have to be re-highlighted.
120
    function insertPart(part){
121
      var text = "\n";
122
      if (part.nodeType == 3) {
123
        select.snapshotChanged();
124
        part = makePartSpan(part, owner);
125
        text = part.currentText;
126
      }
127
      part.dirty = true;
128
      nodeQueue.push(part);
129
      point(part);
130
      return text;
131
    }
132
133
    // Extract the text and newlines from a DOM node, insert them into
134
    // the document, and yield the textual content. Used to replace
135
    // non-normalized nodes.
136
    function writeNode(node, c){
137
      var toYield = [];
138
      forEach(simplifyDOM(node), function(part) {
139
        toYield.push(insertPart(part));
140
      });
141
      return yield(toYield.join(""), c);
142
    }
143
144
    // Check whether a node is a normalized <span> element.
145
    function partNode(node){
146
      if (node.nodeName == "SPAN" && node.childNodes.length == 1 && node.firstChild.nodeType == 3 && node.isPart) {
147
        node.currentText = node.firstChild.nodeValue;
148
        return !/[\n\t\r]/.test(node.currentText);
149
      }
150
      return false;
151
    }
152
153
    // Handle a node. Add its successor to the continuation if there
154
    // is one, find out whether the node is normalized. If it is,
155
    // yield its content, otherwise, normalize it (writeNode will take
156
    // care of yielding).
157
    function scanNode(node, c){
158
      if (node.nextSibling)
159
        c = push(scanNode, node.nextSibling, c);
160
161
      if (partNode(node)){
162
        nodeQueue.push(node);
163
        return yield(node.currentText, c);
164
      }
165
      else if (node.nodeName == "BR") {
166
        nodeQueue.push(node);
167
        return yield("\n", c);
168
      }
169
      else {
170
        point = pointAt(node);
171
        removeElement(node);
172
        return writeNode(node, c);
173
      }
174
    }
175
176
    // MochiKit iterators are objects with a next function that
177
    // returns the next value or throws StopIteration when there are
178
    // no more values.
179
    return {next: function(){return cc();}, nodes: nodeQueue};
180
  }
181
182
  // Determine the text size of a processed node.
183
  function nodeSize(node) {
184
    if (node.nodeName == "BR")
185
      return 1;
186
    else
187
      return node.currentText.length;
188
  }
189
190
  // Search backwards through the top-level nodes until the next BR or
191
  // the start of the frame.
192
  function startOfLine(node) {
193
    while (node && node.nodeName != "BR") node = node.previousSibling;
194
    return node;
195
  }
196
  function endOfLine(node, container) {
197
    if (!node) node = container.firstChild;
198
    while (node && node.nodeName != "BR") node = node.nextSibling;
199
    return node;
200
  }
201
202
  function cleanText(text) {
203
    return text.replace(/\u00a0/g, " ");
204
  }
205
206
  // Client interface for searching the content of the editor. Create
207
  // these by calling CodeMirror.getSearchCursor. To use, call
208
  // findNext on the resulting object -- this returns a boolean
209
  // indicating whether anything was found, and can be called again to
210
  // skip to the next find. Use the select and replace methods to
211
  // actually do something with the found locations.
212
  function SearchCursor(editor, string, fromCursor) {
213
    this.editor = editor;
214
    this.history = editor.history;
215
    this.history.commit();
216
217
    // Are we currently at an occurrence of the search string?
218
    this.atOccurrence = false;
219
    // The object stores a set of nodes coming after its current
220
    // position, so that when the current point is taken out of the
221
    // DOM tree, we can still try to continue.
222
    this.fallbackSize = 15;
223
    var cursor;
224
    // Start from the cursor when specified and a cursor can be found.
225
    if (fromCursor && (cursor = select.cursorPos(this.editor.container))) {
226
      this.line = cursor.node;
227
      this.offset = cursor.offset;
228
    }
229
    else {
230
      this.line = null;
231
      this.offset = 0;
232
    }
233
    this.valid = !!string;
234
235
    // Create a matcher function based on the kind of string we have.
236
    var target = string.split("\n"), self = this;;
237
    this.matches = (target.length == 1) ?
238
      // For one-line strings, searching can be done simply by calling
239
      // indexOf on the current line.
240
      function() {
241
        var match = cleanText(self.history.textAfter(self.line).slice(self.offset)).indexOf(string);
242
        if (match > -1)
243
          return {from: {node: self.line, offset: self.offset + match},
244
                  to: {node: self.line, offset: self.offset + match + string.length}};
245
      } :
246
      // Multi-line strings require internal iteration over lines, and
247
      // some clunky checks to make sure the first match ends at the
248
      // end of the line and the last match starts at the start.
249
      function() {
250
        var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset));
251
        var match = firstLine.lastIndexOf(target[0]);
252
        if (match == -1 || match != firstLine.length - target[0].length)
253
          return false;
254
        var startOffset = self.offset + match;
255
256
        var line = self.history.nodeAfter(self.line);
257
        for (var i = 1; i < target.length - 1; i++) {
258
          if (cleanText(self.history.textAfter(line)) != target[i])
259
            return false;
260
          line = self.history.nodeAfter(line);
261
        }
262
263
        if (cleanText(self.history.textAfter(line)).indexOf(target[target.length - 1]) != 0)
264
          return false;
265
266
        return {from: {node: self.line, offset: startOffset},
267
                to: {node: line, offset: target[target.length - 1].length}};
268
      };
269
  }
270
271
  SearchCursor.prototype = {
272
    findNext: function() {
273
      if (!this.valid) return false;
274
      this.atOccurrence = false;
275
      var self = this;
276
277
      // Go back to the start of the document if the current line is
278
      // no longer in the DOM tree.
279
      if (this.line && !this.line.parentNode) {
280
        this.line = null;
281
        this.offset = 0;
282
      }
283
284
      // Set the cursor's position one character after the given
285
      // position.
286
      function saveAfter(pos) {
287
        if (self.history.textAfter(pos.node).length < pos.offset) {
288
          self.line = pos.node;
289
          self.offset = pos.offset + 1;
290
        }
291
        else {
292
          self.line = self.history.nodeAfter(pos.node);
293
          self.offset = 0;
294
        }
295
      }
296
297
      while (true) {
298
        var match = this.matches();
299
        // Found the search string.
300
        if (match) {
301
          this.atOccurrence = match;
302
          saveAfter(match.from);
303
          return true;
304
        }
305
        this.line = this.history.nodeAfter(this.line);
306
        this.offset = 0;
307
        // End of document.
308
        if (!this.line) {
309
          this.valid = false;
310
          return false;
311
        }
312
      }
313
    },
314
315
    select: function() {
316
      if (this.atOccurrence) {
317
        select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to);
318
        select.scrollToCursor(this.editor.container);
319
      }
320
    },
321
322
    replace: function(string) {
323
      if (this.atOccurrence) {
324
        var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string);
325
        this.line = end.node;
326
        this.offset = end.offset;
327
        this.atOccurrence = false;
328
      }
329
    }
330
  };
331
332
  // The Editor object is the main inside-the-iframe interface.
333
  function Editor(options) {
334
    this.options = options;
335
    this.parent = parent;
336
    this.doc = document;
337
    this.container = this.doc.body;
338
    this.win = window;
339
    this.history = new History(this.container, options.undoDepth, options.undoDelay,
340
                               this, options.onChange);
341
    var self = this;
342
343
    if (!Editor.Parser)
344
      throw "No parser loaded.";
345
    if (options.parserConfig && Editor.Parser.configure)
346
      Editor.Parser.configure(options.parserConfig);
347
348
    if (!options.textWrapping)
349
      this.container.style.whiteSpace = "pre";
350
    setWhiteSpaceModel(options.textWrapping);
351
352
    if (!options.readOnly)
353
      select.setCursorPos(this.container, {node: null, offset: 0});
354
355
    this.dirty = [];
356
    if (options.content)
357
      this.importCode(options.content);
358
    else // FF acts weird when the editable document is completely empty
359
      this.container.appendChild(this.doc.createElement("BR"));
360
361
    if (!options.readOnly) {
362
      if (options.continuousScanning !== false) {
363
        this.scanner = this.documentScanner(options.linesPerPass);
364
        this.delayScanning();
365
      }
366
367
      function setEditable() {
368
        // In IE, designMode frames can not run any scripts, so we use
369
        // contentEditable instead.
370
        if (document.body.contentEditable != undefined && /MSIE/.test(navigator.userAgent))
371
          document.body.contentEditable = "true";
372
        else
373
          document.designMode = "on";
374
      }
375
376
      // If setting the frame editable fails, try again when the user
377
      // focus it (happens when the frame is not visible on
378
      // initialisation, in Firefox).
379
      try {
380
        setEditable();
381
      }
382
      catch(e) {
383
        var focusEvent = addEventHandler(document, "focus", function() {
384
          focusEvent();
385
          setEditable();
386
        }, true);
387
      }
388
389
      addEventHandler(document, "keydown", method(this, "keyDown"));
390
      addEventHandler(document, "keypress", method(this, "keyPress"));
391
      addEventHandler(document, "keyup", method(this, "keyUp"));
392
393
      function cursorActivity() {self.cursorActivity(false);}
394
      addEventHandler(document.body, "paste", cursorActivity);
395
      addEventHandler(document.body, "cut", cursorActivity);
396
      addEventHandler(document.body, "mouseup", cursorActivity);
397
398
      if (this.options.autoMatchParens)
399
        addEventHandler(document.body, "click", method(this, "scheduleParenBlink"));
400
    }
401
  }
402
403
  function isSafeKey(code) {
404
    return (code >= 16 && code <= 18) || // shift, control, alt
405
           (code >= 33 && code <= 40); // arrows, home, end
406
  }
407
408
  Editor.prototype = {
409
    // Import a piece of code into the editor.
410
    importCode: function(code) {
411
      this.history.push(null, null, asEditorLines(code));
412
      this.history.reset();
413
    },
414
415
    // Extract the code from the editor.
416
    getCode: function() {
417
      if (!this.container.firstChild)
418
        return "";
419
420
      var accum = [];
421
      select.markSelection(this.win);
422
      forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
423
      select.selectMarked();
424
      return cleanText(accum.join(""));
425
    },
426
427
    checkLine: function(node) {
428
      if (node === false || !(node == null || node.parentNode == this.container))
429
        throw parent.CodeMirror.InvalidLineHandle;
430
    },
431
432
    cursorPosition: function(start) {
433
      if (start == null) start = true;
434
      var pos = select.cursorPos(this.container, start);
435
      if (pos) return {line: pos.node, character: pos.offset};
436
      else return {line: null, character: 0};
437
    },
438
439
    firstLine: function() {
440
      return null;
441
    },
442
443
    lastLine: function() {
444
      if (this.container.lastChild) return startOfLine(this.container.lastChild);
445
      else return null;
446
    },
447
448
    nextLine: function(line) {
449
      this.checkLine(line);
450
      var end = endOfLine(line ? line.nextSibling : this.container.firstChild, this.container);
451
      return end || false;
452
    },
453
454
    prevLine: function(line) {
455
      this.checkLine(line);
456
      if (line == null) return false;
457
      return startOfLine(line.previousSibling);
458
    },
459
460
    selectLines: function(startLine, startOffset, endLine, endOffset) {
461
      this.checkLine(startLine);
462
      var start = {node: startLine, offset: startOffset}, end = null;
463
      if (endOffset !== undefined) {
464
        this.checkLine(endLine);
465
        end = {node: endLine, offset: endOffset};
466
      }
467
      select.setCursorPos(this.container, start, end);
468
    },
469
470
    lineContent: function(line) {
471
      this.checkLine(line);
472
      var accum = [];
473
      for (line = line ? line.nextSibling : this.container.firstChild;
474
           line && line.nodeName != "BR"; line = line.nextSibling)
475
        accum.push(line.innerText || line.textContent || line.nodeValue || "");
476
      return cleanText(accum.join(""));
477
    },
478
479
    setLineContent: function(line, content) {
480
      this.history.commit();
481
      this.replaceRange({node: line, offset: 0},
482
                        {node: line, offset: this.history.textAfter(line).length},
483
                        content);
484
      this.addDirtyNode(line);
485
      this.scheduleHighlight();
486
    },
487
488
    insertIntoLine: function(line, position, content) {
489
      var before = null;
490
      if (position == "end") {
491
        before = endOfLine(line ? line.nextSibling : this.container.firstChild, this.container);
492
      }
493
      else {
494
        for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) {
495
          if (position == 0) {
496
            before = cur;
497
            break;
498
          }
499
          var text = (cur.innerText || cur.textContent || cur.nodeValue || "");
500
          if (text.length > position) {
501
            before = cur.nextSibling;
502
            content = text.slice(0, position) + content + text.slice(position);
503
            removeElement(cur);
504
            break;
505
          }
506
          position -= text.length;
507
        }
508
      }
509
510
      var lines = asEditorLines(content), doc = this.container.ownerDocument;
511
      for (var i = 0; i < lines.length; i++) {
512
        if (i > 0) this.container.insertBefore(doc.createElement("BR"), before);
513
        this.container.insertBefore(makePartSpan(lines[i], doc), before);
514
      }
515
      this.addDirtyNode(line);
516
      this.scheduleHighlight();
517
    },
518
519
    // Retrieve the selected text.
520
    selectedText: function() {
521
      var h = this.history;
522
      h.commit();
523
524
      var start = select.cursorPos(this.container, true),
525
          end = select.cursorPos(this.container, false);
526
      if (!start || !end) return "";
527
528
      if (start.node == end.node)
529
        return h.textAfter(start.node).slice(start.offset, end.offset);
530
531
      var text = [h.textAfter(start.node).slice(start.offset)];
532
      for (pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos))
533
        text.push(h.textAfter(pos));
534
      text.push(h.textAfter(end.node).slice(0, end.offset));
535
      return cleanText(text.join("\n"));
536
    },
537
538
    // Replace the selection with another piece of text.
539
    replaceSelection: function(text) {
540
      this.history.commit();
541
      var start = select.cursorPos(this.container, true),
542
          end = select.cursorPos(this.container, false);
543
      if (!start || !end) return;
544
545
      end = this.replaceRange(start, end, text);
546
      select.setCursorPos(this.container, start, end);
547
    },
548
549
    replaceRange: function(from, to, text) {
550
      var lines = asEditorLines(text);
551
      lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
552
      var lastLine = lines[lines.length - 1];
553
      lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
554
      var end = this.history.nodeAfter(to.node);
555
      this.history.push(from.node, end, lines);
556
      return {node: this.history.nodeBefore(end),
557
              offset: lastLine.length};
558
    },
559
560
    getSearchCursor: function(string, fromCursor) {
561
      return new SearchCursor(this, string, fromCursor);
562
    },
563
564
    // Re-indent the whole buffer
565
    reindent: function() {
566
      if (this.container.firstChild)
567
        this.indentRegion(null, this.container.lastChild);
568
    },
569
570
    // Intercept enter and tab, and assign their new functions.
571
    keyDown: function(event) {
572
      // Don't scan when the user is typing.
573
      this.delayScanning();
574
      // Schedule a paren-highlight event, if configured.
575
      if (this.options.autoMatchParens)
576
        this.scheduleParenBlink();
577
578
      if (event.keyCode == 13) { // enter
579
        if (event.ctrlKey) {
580
          this.reparseBuffer();
581
        }
582
        else {
583
          select.insertNewlineAtCursor(this.win);
584
          this.indentAtCursor();
585
          select.scrollToCursor(this.container);
586
        }
587
        event.stop();
588
      }
589
      else if (event.keyCode == 9) { // tab
590
        this.handleTab(!event.ctrlKey && !event.shiftKey);
591
        event.stop();
592
      }
593
      else if (event.ctrlKey || event.metaKey) {
594
        if (event.keyCode == 90 || event.keyCode == 8) { // Z, backspace
595
          this.history.undo();
596
          event.stop();
597
        }
598
        else if (event.keyCode == 89) { // Y
599
          this.history.redo();
600
          event.stop();
601
        }
602
        else if (event.keyCode == 83 && this.options.saveFunction) { // S
603
          this.options.saveFunction();
604
          event.stop();
605
        }
606
      }
607
    },
608
609
    // Check for characters that should re-indent the current line,
610
    // and prevent Opera from handling enter and tab anyway.
611
    keyPress: function(event) {
612
      var electric = Editor.Parser.electricChars;
613
      // Hack for Opera, and Firefox on OS X, in which stopping a
614
      // keydown event does not prevent the associated keypress event
615
      // from happening, so we have to cancel enter and tab again
616
      // here.
617
      if (event.code == 13 || event.code == 9)
618
        event.stop();
619
      else if ((event.character == "[" || event.character == "]") && event.ctrlKey)
620
        event.stop(), this.blinkParens();
621
      else if (electric && electric.indexOf(event.character) != -1)
622
        this.parent.setTimeout(method(this, "indentAtCursor"), 0);
623
    },
624
625
    // Mark the node at the cursor dirty when a non-safe key is
626
    // released.
627
    keyUp: function(event) {
628
      this.cursorActivity(isSafeKey(event.keyCode));
629
    },
630
631
    // Indent the line following a given <br>, or null for the first
632
    // line. If given a <br> element, this must have been highlighted
633
    // so that it has an indentation method. Returns the whitespace
634
    // element that has been modified or created (if any).
635
    indentLineAfter: function(start, direction) {
636
      // whiteSpace is the whitespace span at the start of the line,
637
      // or null if there is no such node.
638
      var whiteSpace = start ? start.nextSibling : this.container.firstChild;
639
      if (whiteSpace && !hasClass(whiteSpace, "whitespace"))
640
        whiteSpace = null;
641
642
      // Sometimes the start of the line can influence the correct
643
      // indentation, so we retrieve it.
644
      var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
645
      var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
646
647
      // Ask the lexical context for the correct indentation, and
648
      // compute how much this differs from the current indentation.
649
      var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
650
      if (start) newIndent = start.indentation(nextChars, curIndent, direction);
651
      else if (Editor.Parser.firstIndentation) newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
652
      var indentDiff = newIndent - curIndent;
653
654
      // If there is too much, this is just a matter of shrinking a span.
655
      if (indentDiff < 0) {
656
        if (newIndent == 0) {
657
          if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild, 0);
658
          removeElement(whiteSpace);
659
          whiteSpace = null;
660
        }
661
        else {
662
          select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
663
          whiteSpace.currentText = safeWhiteSpace(newIndent);
664
          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
665
        }
666
      }
667
      // Not enough...
668
      else if (indentDiff > 0) {
669
        // If there is whitespace, we grow it.
670
        if (whiteSpace) {
671
          whiteSpace.currentText = safeWhiteSpace(newIndent);
672
          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
673
        }
674
        // Otherwise, we have to add a new whitespace node.
675
        else {
676
          whiteSpace = makePartSpan(safeWhiteSpace(newIndent), this.doc);
677
          whiteSpace.className = "whitespace";
678
          if (start) insertAfter(whiteSpace, start);
679
          else this.container.insertBefore(whiteSpace, this.container.firstChild);
680
        }
681
        if (firstText) select.snapshotMove(firstText.firstChild, whiteSpace.firstChild, curIndent, false, true);
682
      }
683
      if (indentDiff != 0) this.addDirtyNode(start);
684
      return whiteSpace;
685
    },
686
687
    // Re-highlight the selected part of the document.
688
    highlightAtCursor: function() {
689
      var pos = select.selectionTopNode(this.container, true);
690
      var to = select.selectionTopNode(this.container, false);
691
      if (pos === false || !to) return;
692
      // Skip one node ahead to make sure the cursor itself is
693
      // *inside* a highlighted line.
694
      if (to.nextSibling) to = to.nextSibling;
695
696
      select.markSelection(this.win);
697
      var toIsText = to.nodeType == 3;
698
      if (!toIsText) to.dirty = true;
699
700
      // Highlight lines as long as to is in the document and dirty.
701
      while (to.parentNode == this.container && (toIsText || to.dirty)) {
702
        var result = this.highlight(pos, 1, true);
703
        if (result) pos = result.node;
704
        if (!result || result.left) break;
705
      }
706
      select.selectMarked();
707
    },
708
709
    // When tab is pressed with text selected, the whole selection is
710
    // re-indented, when nothing is selected, the line with the cursor
711
    // is re-indented.
712
    handleTab: function(direction) {
713
      if (this.options.dumbTabs) {
714
        select.insertTabAtCursor(this.win);
715
      }
716
      else if (!select.somethingSelected(this.win)) {
717
        this.indentAtCursor(direction);
718
      }
719
      else {
720
        var start = select.selectionTopNode(this.container, true),
721
            end = select.selectionTopNode(this.container, false);
722
        if (start === false || end === false) return;
723
        this.indentRegion(start, end, direction);
724
      }
725
    },
726
727
    // Delay (or initiate) the next paren blink event.
728
    scheduleParenBlink: function() {
729
      if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
730
      this.parenEvent = this.parent.setTimeout(method(this, "blinkParens"), 300);
731
    },
732
733
    isNearParsedNode: function(node) {
734
      var distance = 0;
735
      while (node && (!node.parserFromHere || node.dirty)) {
736
        distance += (node.textContent || node.innerText || "-").length;
737
        if (distance > 800) return false;
738
        node = node.previousSibling;
739
      }
740
      return true;
741
    },
742
743
    // Take the token before the cursor. If it contains a character in
744
    // '()[]{}', search for the matching paren/brace/bracket, and
745
    // highlight them in green for a moment, or red if no proper match
746
    // was found.
747
    blinkParens: function() {
748
      // Clear the event property.
749
      if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
750
      this.parenEvent = null;
751
752
      // Extract a 'paren' from a piece of text.
753
      function paren(node) {
754
        if (node.currentText) {
755
          var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
756
          return match && match[1];
757
        }
758
      }
759
      // Determine the direction a paren is facing.
760
      function forward(ch) {
761
        return /[\(\[\{]/.test(ch);
762
      }
763
764
      var ch, self = this, cursor = select.selectionTopNode(this.container, true);
765
      if (!cursor || !this.isNearParsedNode(cursor)) return;
766
      this.highlightAtCursor();
767
      cursor = select.selectionTopNode(this.container, true);
768
      if (!cursor || !(ch = paren(cursor))) return;
769
      // We only look for tokens with the same className.
770
      var className = cursor.className, dir = forward(ch), match = matching[ch];
771
772
      // Since parts of the document might not have been properly
773
      // highlighted, and it is hard to know in advance which part we
774
      // have to scan, we just try, and when we find dirty nodes we
775
      // abort, parse them, and re-try.
776
      function tryFindMatch() {
777
        var stack = [], ch, ok = true;;
778
        for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
779
          if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) {
780
            if (forward(ch) == dir)
781
              stack.push(ch);
782
            else if (!stack.length)
783
              ok = false;
784
            else if (stack.pop() != matching[ch])
785
              ok = false;
786
            if (!stack.length) break;
787
          }
788
          else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") {
789
            return {node: runner, status: "dirty"};
790
          }
791
        }
792
        return {node: runner, status: runner && ok};
793
      }
794
      // Temporarily give the relevant nodes a colour.
795
      function blink(node, ok) {
796
        node.style.fontWeight = "bold";
797
        node.style.color = ok ? "#8F8" : "#F88";
798
        self.parent.setTimeout(function() {node.style.fontWeight = ""; node.style.color = "";}, 500);
799
      }
800
801
      while (true) {
802
        var found = tryFindMatch();
803
        if (found.status == "dirty") {
804
          this.highlight(found.node, 1);
805
          // Needed because in some corner cases a highlight does not
806
          // reach a node.
807
          found.node.dirty = false;
808
          continue;
809
        }
810
        else {
811
          blink(cursor, found.status);
812
          if (found.node) blink(found.node, found.status);
813
          break;
814
        }
815
      }
816
    },
817
818
    // Adjust the amount of whitespace at the start of the line that
819
    // the cursor is on so that it is indented properly.
820
    indentAtCursor: function(direction) {
821
      if (!this.container.firstChild) return;
822
      // The line has to have up-to-date lexical information, so we
823
      // highlight it first.
824
      this.highlightAtCursor();
825
      var cursor = select.selectionTopNode(this.container, false);
826
      // If we couldn't determine the place of the cursor,
827
      // there's nothing to indent.
828
      if (cursor === false)
829
        return;
830
      var lineStart = startOfLine(cursor);
831
      var whiteSpace = this.indentLineAfter(lineStart, direction);
832
      if (cursor == lineStart && whiteSpace)
833
          cursor = whiteSpace;
834
      // This means the indentation has probably messed up the cursor.
835
      if (cursor == whiteSpace)
836
        select.focusAfterNode(cursor, this.container);
837
    },
838
839
    // Indent all lines whose start falls inside of the current
840
    // selection.
841
    indentRegion: function(current, end, direction) {
842
      select.markSelection(this.win);
843
      current = startOfLine(current);
844
      end = endOfLine(end, this.container);
845
846
      do {
847
        this.highlight(current);
848
        var hl = this.highlight(current, 1);
849
        this.indentLineAfter(current, direction);
850
        current = hl ? hl.node : null;
851
      } while (current != end);
852
      select.selectMarked();
853
    },
854
855
    // Find the node that the cursor is in, mark it as dirty, and make
856
    // sure a highlight pass is scheduled.
857
    cursorActivity: function(safe) {
858
      if (internetExplorer) {
859
        this.container.createTextRange().execCommand("unlink");
860
        this.selectionSnapshot = select.selectionCoords(this.win);
861
      }
862
863
      var activity = this.options.cursorActivity;
864
      if (!safe || activity) {
865
        var cursor = select.selectionTopNode(this.container, false);
866
        if (cursor === false || !this.container.firstChild) return;
867
        cursor = cursor || this.container.firstChild;
868
        if (activity) activity(cursor);
869
        if (!safe) {
870
          this.scheduleHighlight();
871
          this.addDirtyNode(cursor);
872
        }
873
      }
874
    },
875
876
    reparseBuffer: function() {
877
      forEach(this.container.childNodes, function(node) {node.dirty = true;});
878
      if (this.container.firstChild)
879
        this.addDirtyNode(this.container.firstChild);
880
    },
881
882
    // Add a node to the set of dirty nodes, if it isn't already in
883
    // there.
884
    addDirtyNode: function(node) {
885
      node = node || this.container.firstChild;
886
      if (!node) return;
887
888
      for (var i = 0; i < this.dirty.length; i++)
889
        if (this.dirty[i] == node) return;
890
891
      if (node.nodeType != 3)
892
        node.dirty = true;
893
      this.dirty.push(node);
894
    },
895
896
    // Cause a highlight pass to happen in options.passDelay
897
    // milliseconds. Clear the existing timeout, if one exists. This
898
    // way, the passes do not happen while the user is typing, and
899
    // should as unobtrusive as possible.
900
    scheduleHighlight: function() {
901
      // Timeouts are routed through the parent window, because on
902
      // some browsers designMode windows do not fire timeouts.
903
      var self = this;
904
      this.parent.clearTimeout(this.highlightTimeout);
905
      this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
906
    },
907
908
    // Fetch one dirty node, and remove it from the dirty set.
909
    getDirtyNode: function() {
910
      while (this.dirty.length > 0) {
911
        var found = this.dirty.pop();
912
        // IE8 sometimes throws an unexplainable 'invalid argument'
913
        // exception for found.parentNode
914
        try {
915
          // If the node has been coloured in the meantime, or is no
916
          // longer in the document, it should not be returned.
917
          while (found && found.parentNode != this.container)
918
            found = found.parentNode
919
          if (found && (found.dirty || found.nodeType == 3))
920
            return found;
921
        } catch (e) {}
922
      }
923
      return null;
924
    },
925
926
    // Pick dirty nodes, and highlight them, until
927
    // options.linesPerPass lines have been highlighted. The highlight
928
    // method will continue to next lines as long as it finds dirty
929
    // nodes. It returns an object indicating the amount of lines
930
    // left, and information about the place where it stopped. If
931
    // there are dirty nodes left after this function has spent all
932
    // its lines, it shedules another highlight to finish the job.
933
    highlightDirty: function(force) {
934
      var lines = force ? Infinity : this.options.linesPerPass;
935
      if (!this.options.readOnly) select.markSelection(this.win);
936
      var start;
937
      while (lines > 0 && (start = this.getDirtyNode())){
938
        var result = this.highlight(start, lines);
939
        if (result) {
940
          lines = result.left;
941
          if (result.node && result.dirty)
942
            this.addDirtyNode(result.node);
943
        }
944
      }
945
      if (!this.options.readOnly) select.selectMarked();
946
      if (start)
947
        this.scheduleHighlight();
948
      return this.dirty.length == 0;
949
    },
950
951
    // Creates a function that, when called through a timeout, will
952
    // continuously re-parse the document.
953
    documentScanner: function(linesPer) {
954
      var self = this, pos = null;
955
      return function() {
956
        // If the current node is no longer in the document... oh
957
        // well, we start over.
958
        if (pos && pos.parentNode != self.container)
959
          pos = null;
960
        select.markSelection(self.win);
961
        var result = self.highlight(pos, linesPer, true);
962
        select.selectMarked();
963
        var newPos = result ? (result.node && result.node.nextSibling) : null;
964
        pos = (pos == newPos) ? null : newPos;
965
        self.delayScanning();
966
      };
967
    },
968
969
    // Starts the continuous scanning process for this document after
970
    // a given interval.
971
    delayScanning: function() {
972
      if (this.scanner) {
973
        this.parent.clearTimeout(this.documentScan);
974
        this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
975
      }
976
    },
977
978
    // The function that does the actual highlighting/colouring (with
979
    // help from the parser and the DOM normalizer). Its interface is
980
    // rather overcomplicated, because it is used in different
981
    // situations: ensuring that a certain line is highlighted, or
982
    // highlighting up to X lines starting from a certain point. The
983
    // 'from' argument gives the node at which it should start. If
984
    // this is null, it will start at the beginning of the frame. When
985
    // a number of lines is given with the 'lines' argument, it will
986
    // colour no more than that amount. If at any time it comes across
987
    // a 'clean' line (no dirty nodes), it will stop, except when
988
    // 'cleanLines' is true.
989
    highlight: function(from, lines, cleanLines){
990
      var container = this.container, self = this, active = this.options.activeTokens, origFrom = from;
991
992
      if (!container.firstChild)
993
        return;
994
      // lines given as null means 'make sure this BR node has up to date parser information'
995
      if (lines == null) {
996
        if (!from) return;
997
        else from = from.previousSibling;
998
      }
999
      // Backtrack to the first node before from that has a partial
1000
      // parse stored.
1001
      while (from && (!from.parserFromHere || from.dirty))
1002
        from = from.previousSibling;
1003
      // If we are at the end of the document, do nothing.
1004
      if (from && !from.nextSibling)
1005
        return;
1006
1007
      // Check whether a part (<span> node) and the corresponding token
1008
      // match.
1009
      function correctPart(token, part){
1010
        return !part.reduced && part.currentText == token.value && part.className == token.style;
1011
      }
1012
      // Shorten the text associated with a part by chopping off
1013
      // characters from the front. Note that only the currentText
1014
      // property gets changed. For efficiency reasons, we leave the
1015
      // nodeValue alone -- we set the reduced flag to indicate that
1016
      // this part must be replaced.
1017
      function shortenPart(part, minus){
1018
        part.currentText = part.currentText.substring(minus);
1019
        part.reduced = true;
1020
      }
1021
      // Create a part corresponding to a given token.
1022
      function tokenPart(token){
1023
        var part = makePartSpan(token.value, self.doc);
1024
        part.className = token.style;
1025
        return part;
1026
      }
1027
1028
      // Get the token stream. If from is null, we start with a new
1029
      // parser from the start of the frame, otherwise a partial parse
1030
      // is resumed.
1031
      var traversal = traverseDOM(from ? from.nextSibling : container.firstChild),
1032
          stream = stringStream(traversal),
1033
          parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream);
1034
1035
      // parts is an interface to make it possible to 'delay' fetching
1036
      // the next DOM node until we are completely done with the one
1037
      // before it. This is necessary because often the next node is
1038
      // not yet available when we want to proceed past the current
1039
      // one.
1040
      var parts = {
1041
        current: null,
1042
        // Fetch current node.
1043
        get: function(){
1044
          if (!this.current)
1045
            this.current = traversal.nodes.shift();
1046
          return this.current;
1047
        },
1048
        // Advance to the next part (do not fetch it yet).
1049
        next: function(){
1050
          this.current = null;
1051
        },
1052
        // Remove the current part from the DOM tree, and move to the
1053
        // next.
1054
        remove: function(){
1055
          container.removeChild(this.get());
1056
          this.current = null;
1057
        },
1058
        // Advance to the next part that is not empty, discarding empty
1059
        // parts.
1060
        getNonEmpty: function(){
1061
          var part = this.get();
1062
          // Allow empty nodes when they are alone on a line, needed
1063
          // for the FF cursor bug workaround (see select.js,
1064
          // insertNewlineAtCursor).
1065
          while (part && part.nodeName == "SPAN" && part.currentText == "") {
1066
            var old = part;
1067
            this.remove();
1068
            part = this.get();
1069
            // Adjust selection information, if any. See select.js for details.
1070
            select.snapshotMove(old.firstChild, part.firstChild || part, 0);
1071
          }
1072
          return part;
1073
        }
1074
      };
1075
1076
      var lineDirty = false, prevLineDirty = true, lineNodes = 0;
1077
1078
      // This forEach loops over the tokens from the parsed stream, and
1079
      // at the same time uses the parts object to proceed through the
1080
      // corresponding DOM nodes.
1081
      forEach(parsed, function(token){
1082
        var part = parts.getNonEmpty();
1083
1084
        if (token.value == "\n"){
1085
          // The idea of the two streams actually staying synchronized
1086
          // is such a long shot that we explicitly check.
1087
          if (part.nodeName != "BR")
1088
            throw "Parser out of sync. Expected BR.";
1089
1090
          if (part.dirty || !part.indentation) lineDirty = true;
1091
          if (lineDirty) self.history.touch(from);
1092
          from = part;
1093
1094
          // Every <br> gets a copy of the parser state and a lexical
1095
          // context assigned to it. The first is used to be able to
1096
          // later resume parsing from this point, the second is used
1097
          // for indentation.
1098
          part.parserFromHere = parsed.copy();
1099
          part.indentation = token.indentation;
1100
          part.dirty = false;
1101
1102
          // No line argument passed means 'go at least until this node'.
1103
          if (lines == null && part == origFrom) throw StopIteration;
1104
1105
          // A clean line with more than one node means we are done.
1106
          // Throwing a StopIteration is the way to break out of a
1107
          // MochiKit forEach loop.
1108
          if ((lines !== undefined && --lines <= 0) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines))
1109
            throw StopIteration;
1110
          prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0;
1111
          parts.next();
1112
        }
1113
        else {
1114
          if (part.nodeName != "SPAN")
1115
            throw "Parser out of sync. Expected SPAN.";
1116
          if (part.dirty)
1117
            lineDirty = true;
1118
          lineNodes++;
1119
1120
          // If the part matches the token, we can leave it alone.
1121
          if (correctPart(token, part)){
1122
            part.dirty = false;
1123
            parts.next();
1124
          }
1125
          // Otherwise, we have to fix it.
1126
          else {
1127
            lineDirty = true;
1128
            // Insert the correct part.
1129
            var newPart = tokenPart(token);
1130
            container.insertBefore(newPart, part);
1131
            if (active) active(newPart, token, self);
1132
            var tokensize = token.value.length;
1133
            var offset = 0;
1134
            // Eat up parts until the text for this token has been
1135
            // removed, adjusting the stored selection info (see
1136
            // select.js) in the process.
1137
            while (tokensize > 0) {
1138
              part = parts.get();
1139
              var partsize = part.currentText.length;
1140
              select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
1141
              if (partsize > tokensize){
1142
                shortenPart(part, tokensize);
1143
                tokensize = 0;
1144
              }
1145
              else {
1146
                tokensize -= partsize;
1147
                offset += partsize;
1148
                parts.remove();
1149
              }
1150
            }
1151
          }
1152
        }
1153
      });
1154
      if (lineDirty) this.history.touch(from);
1155
1156
      // The function returns some status information that is used by
1157
      // hightlightDirty to determine whether and where it has to
1158
      // continue.
1159
      return {left: lines,
1160
              node: parts.get(),
1161
              dirty: lineDirty};
1162
    }
1163
  };
1164
1165
  return Editor;
1166
})();
1167
1168
addEventHandler(window, "load", function() {
1169
  var CodeMirror = window.frameElement.CodeMirror;
1170
  CodeMirror.editor = new Editor(CodeMirror.options);
1171
  if (CodeMirror.options.initCallback) {
1172
    this.parent.setTimeout(function(){
1173
      CodeMirror.options.initCallback(CodeMirror);
1174
    }, 0);
1175
  }
1176
});
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/mirrorframe.js (+81 lines)
Line 0 Link Here
1
/* Demonstration of embedding CodeMirror in a bigger application. The
2
 * interface defined here is a mess of prompts and confirms, and
3
 * should probably not be used in a real project.
4
 */
5
6
function MirrorFrame(place, options) {
7
  this.home = document.createElement("DIV");
8
  if (place.appendChild)
9
    place.appendChild(this.home);
10
  else
11
    place(this.home);
12
13
  var self = this;
14
  function makeButton(name, action) {
15
    var button = document.createElement("INPUT");
16
    button.type = "button";
17
    button.value = name;
18
    self.home.appendChild(button);
19
    button.onclick = function(){self[action].call(self);};
20
  }
21
22
  makeButton("Search", "search");
23
  makeButton("Replace", "replace");
24
  makeButton("Current line", "line");
25
  makeButton("Jump to line", "jump");
26
  makeButton("Insert constructor", "macro");
27
  makeButton("Indent all", "reindent");
28
29
  this.mirror = new CodeMirror(this.home, options);
30
}
31
32
MirrorFrame.prototype = {
33
  search: function() {
34
    var text = prompt("Enter search term:", "");
35
    if (!text) return;
36
37
    var first = true;
38
    do {
39
      var cursor = this.mirror.getSearchCursor(text, first);
40
      first = false;
41
      while (cursor.findNext()) {
42
        cursor.select();
43
        if (!confirm("Search again?"))
44
          return;
45
      }
46
    } while (confirm("End of document reached. Start over?"));
47
  },
48
49
  replace: function() {
50
    // This is a replace-all, but it is possible to implement a
51
    // prompting replace.
52
    var from = prompt("Enter search string:", ""), to;
53
    if (from) to = prompt("What should it be replaced with?", "");
54
    if (to == null) return;
55
56
    var cursor = this.mirror.getSearchCursor(from, false);
57
    while (cursor.findNext())
58
      cursor.replace(to);
59
  },
60
61
  jump: function() {
62
    var line = prompt("Jump to line:", "");
63
    if (line && !isNaN(Number(line)))
64
      this.mirror.jumpToLine(Number(line));
65
  },
66
67
  line: function() {
68
    alert("The cursor is currently at line " + this.mirror.currentLine());
69
    this.mirror.focus();
70
  },
71
72
  macro: function() {
73
    var name = prompt("Name your constructor:", "");
74
    if (name)
75
      this.mirror.replaceSelection("function " + name + "() {\n  \n}\n\n" + name + ".prototype = {\n  \n};\n");
76
  },
77
78
  reindent: function() {
79
    this.mirror.reindent();
80
  }
81
};
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsecss.js (+155 lines)
Line 0 Link Here
1
/* Simple parser for CSS */
2
3
var CSSParser = Editor.Parser = (function() {
4
  var tokenizeCSS = (function() {
5
    function normal(source, setState) {
6
      var ch = source.next();
7
      if (ch == "@") {
8
        source.nextWhile(matcher(/\w/));
9
        return "css-at";
10
      }
11
      else if (ch == "/" && source.equals("*")) {
12
        setState(inCComment);
13
        return null;
14
      }
15
      else if (ch == "<" && source.equals("!")) {
16
        setState(inSGMLComment);
17
        return null;
18
      }
19
      else if (ch == "=") {
20
        return "css-compare";
21
      }
22
      else if (source.equals("=") && (ch == "~" || ch == "|")) {
23
        source.next();
24
        return "css-compare";
25
      }
26
      else if (ch == "\"" || ch == "'") {
27
        setState(inString(ch));
28
        return null;
29
      }
30
      else if (ch == "#") {
31
        source.nextWhile(matcher(/\w/));
32
        return "css-hash";
33
      }
34
      else if (ch == "!") {
35
        source.nextWhile(matcher(/[ \t]/));
36
        source.nextWhile(matcher(/\w/));
37
        return "css-important";
38
      }
39
      else if (/\d/.test(ch)) {
40
        source.nextWhile(matcher(/[\w.%]/));
41
        return "css-unit";
42
      }
43
      else if (/[,.+>*\/]/.test(ch)) {
44
        return "css-select-op";
45
      }
46
      else if (/[;{}:\[\]]/.test(ch)) {
47
        return "css-punctuation";
48
      }
49
      else {
50
        source.nextWhile(matcher(/[\w\\\-_]/));
51
        return "css-identifier";
52
      }
53
    }
54
55
    function inCComment(source, setState) {
56
      var maybeEnd = false;
57
      while (!source.endOfLine()) {
58
        var ch = source.next();
59
        if (maybeEnd && ch == "/") {
60
          setState(normal);
61
          break;
62
        }
63
        maybeEnd = (ch == "*");
64
      }
65
      return "css-comment";
66
    }
67
68
    function inSGMLComment(source, setState) {
69
      var dashes = 0;
70
      while (!source.endOfLine()) {
71
        var ch = source.next();
72
        if (dashes >= 2 && ch == ">") {
73
          setState(normal);
74
          break;
75
        }
76
        dashes = (ch == "-") ? dashes + 1 : 0;
77
      }
78
      return "css-comment";
79
    }
80
81
    function inString(quote) {
82
      return function(source, setState) {
83
        var escaped = false;
84
        while (!source.endOfLine()) {
85
          var ch = source.next();
86
          if (ch == quote && !escaped)
87
            break;
88
          escaped = !escaped && ch == "\\";
89
        }
90
        if (!escaped)
91
          setState(normal);
92
        return "css-string";
93
      };
94
    }
95
96
    return function(source, startState) {
97
      return tokenizer(source, startState || normal);
98
    };
99
  })();
100
101
  function indentCSS(inBraces, inRule, base) {
102
    return function(nextChars) {
103
      if (!inBraces || /^\}/.test(nextChars)) return base;
104
      else if (inRule) return base + 4;
105
      else return base + 2;
106
    };
107
  }
108
109
  // This is a very simplistic parser -- since CSS does not really
110
  // nest, it works acceptably well, but some nicer colouroing could
111
  // be provided with a more complicated parser.
112
  function parseCSS(source, basecolumn) {
113
    basecolumn = basecolumn || 0;
114
    var tokens = tokenizeCSS(source);
115
    var inBraces = false, inRule = false;
116
117
    var iter = {
118
      next: function() {
119
        var token = tokens.next(), style = token.style, content = token.content;
120
121
        if (style == "css-identifier" && inRule)
122
          token.style = "css-value";
123
        if (style == "css-hash")
124
          token.style =  inRule ? "css-colorcode" : "css-identifier";
125
126
        if (content == "\n")
127
          token.indentation = indentCSS(inBraces, inRule, basecolumn);
128
129
        if (content == "{")
130
          inBraces = true;
131
        else if (content == "}")
132
          inBraces = inRule = false;
133
        else if (inBraces && content == ";")
134
          inRule = false;
135
        else if (inBraces && style != "css-comment" && style != "whitespace")
136
          inRule = true;
137
138
        return token;
139
      },
140
141
      copy: function() {
142
        var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state;
143
        return function(source) {
144
          tokens = tokenizeCSS(source, _tokenState);
145
          inBraces = _inBraces;
146
          inRule = _inRule;
147
          return iter;
148
        };
149
      }
150
    };
151
    return iter;
152
  }
153
154
  return {make: parseCSS, electricChars: "}"};
155
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsehtmlmixed.js (+73 lines)
Line 0 Link Here
1
var HTMLMixedParser = Editor.Parser = (function() {
2
  if (!(CSSParser && JSParser && XMLParser))
3
    throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work.");
4
  XMLParser.configure({useHTMLKludges: true});
5
6
  function parseMixed(stream) {
7
    var htmlParser = XMLParser.make(stream), localParser = null, inTag = false;
8
    var iter = {next: top, copy: copy};
9
10
    function top() {
11
      var token = htmlParser.next();
12
      if (token.content == "<")
13
        inTag = true;
14
      else if (token.style == "xml-tagname" && inTag === true)
15
        inTag = token.content.toLowerCase();
16
      else if (token.content == ">") {
17
        if (inTag == "script")
18
          iter.next = local(JSParser, "</script");
19
        else if (inTag == "style")
20
          iter.next = local(CSSParser, "</style");
21
        inTag = false;
22
      }
23
      return token;
24
    }
25
    function local(parser, tag) {
26
      var baseIndent = htmlParser.indentation();
27
      localParser = parser.make(stream, baseIndent + 2);
28
      return function() {
29
        if (stream.lookAhead(tag, false, false, true)) {
30
          localParser = null;
31
          iter.next = top;
32
          return top();
33
        }
34
35
        var token = localParser.next();
36
        var lt = token.value.lastIndexOf("<"), sz = Math.min(token.value.length - lt, tag.length);
37
        if (lt != -1 && token.value.slice(lt, lt + sz).toLowerCase() == tag.slice(0, sz) &&
38
            stream.lookAhead(tag.slice(sz), false, false, true)) {
39
          stream.push(token.value.slice(lt));
40
          token.value = token.value.slice(0, lt);
41
        }
42
43
        if (token.indentation) {
44
          var oldIndent = token.indentation;
45
          token.indentation = function(chars) {
46
            if (chars == "</")
47
              return baseIndent;
48
            else
49
              return oldIndent(chars);
50
          }
51
        }
52
53
        return token;
54
      };
55
    }
56
57
    function copy() {
58
      var _html = htmlParser.copy(), _local = localParser && localParser.copy(),
59
          _next = iter.next, _inTag = inTag;
60
      return function(_stream) {
61
        stream = _stream;
62
        htmlParser = _html(_stream);
63
        localParser = _local && _local(_stream);
64
        iter.next = _next;
65
        inTag = _inTag;
66
        return iter;
67
      };
68
    }
69
    return iter;
70
  }
71
72
  return {make: parseMixed, electricChars: "{}/"};
73
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsejavascript.js (+322 lines)
Line 0 Link Here
1
/* Parse function for JavaScript. Makes use of the tokenizer from
2
 * tokenizejavascript.js. Note that your parsers do not have to be
3
 * this complicated -- if you don't want to recognize local variables,
4
 * in many languages it is enough to just look for braces, semicolons,
5
 * parentheses, etc, and know when you are inside a string or comment.
6
 *
7
 * See manual.html for more info about the parser interface.
8
 */
9
10
var JSParser = Editor.Parser = (function() {
11
  // Token types that can be considered to be atoms.
12
  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
13
  // Constructor for the lexical context objects.
14
  function JSLexical(indented, column, type, align, prev) {
15
    // indentation at start of this line
16
    this.indented = indented;
17
    // column at which this scope was opened
18
    this.column = column;
19
    // type of scope ('vardef', 'stat' (statement), 'form' (special form), '[', '{', or '(')
20
    this.type = type;
21
    // '[', '{', or '(' blocks that have any text after their opening
22
    // character are said to be 'aligned' -- any lines below are
23
    // indented all the way to the opening character.
24
    if (align != null)
25
      this.align = align;
26
    // Parent scope, if any.
27
    this.prev = prev;
28
  }
29
  // My favourite JavaScript indentation rules.
30
  function indentJS(lexical) {
31
    return function(firstChars) {
32
      var firstChar = firstChars && firstChars.charAt(0);
33
      var closing = firstChar == lexical.type;
34
      if (lexical.type == "vardef")
35
        return lexical.indented + 4;
36
      else if (lexical.type == "form" && firstChar == "{")
37
        return lexical.indented;
38
      else if (lexical.type == "stat" || lexical.type == "form")
39
        return lexical.indented + 2;
40
      else if (lexical.align)
41
        return lexical.column - (closing ? 1 : 0);
42
      else
43
        return lexical.indented + (closing ? 0 : 2);
44
    };
45
  }
46
47
  // The parser-iterator-producing function itself.
48
  function parseJS(input, basecolumn) {
49
    // Wrap the input in a token stream
50
    var tokens = tokenizeJavaScript(input);
51
    // The parser state. cc is a stack of actions that have to be
52
    // performed to finish the current statement. For example we might
53
    // know that we still need to find a closing parenthesis and a
54
    // semicolon. Actions at the end of the stack go first. It is
55
    // initialized with an infinitely looping action that consumes
56
    // whole statements.
57
    var cc = [statements];
58
    // Context contains information about the current local scope, the
59
    // variables defined in that, and the scopes above it.
60
    var context = null;
61
    // The lexical scope, used mostly for indentation.
62
    var lexical = new JSLexical((basecolumn || 0) - 2, 0, "block", false);
63
    // Current column, and the indentation at the start of the current
64
    // line. Used to create lexical scope objects.
65
    var column = 0;
66
    var indented = 0;
67
    // Variables which are used by the mark, cont, and pass functions
68
    // below to communicate with the driver loop in the 'next'
69
    // function.
70
    var consume, marked;
71
72
    // The iterator object.
73
    var parser = {next: next, copy: copy};
74
75
    function next(){
76
      // Start by performing any 'lexical' actions (adjusting the
77
      // lexical variable), or the operations below will be working
78
      // with the wrong lexical state.
79
      while(cc[cc.length - 1].lex)
80
        cc.pop()();
81
82
      // Fetch a token.
83
      var token = tokens.next();
84
85
      // Adjust column and indented.
86
      if (token.type == "whitespace" && column == 0)
87
        indented = token.value.length;
88
      column += token.value.length;
89
      if (token.content == "\n"){
90
        indented = column = 0;
91
        // If the lexical scope's align property is still undefined at
92
        // the end of the line, it is an un-aligned scope.
93
        if (!("align" in lexical))
94
          lexical.align = false;
95
        // Newline tokens get an indentation function associated with
96
        // them.
97
        token.indentation = indentJS(lexical);
98
      }
99
      // No more processing for meaningless tokens.
100
      if (token.type == "whitespace" || token.type == "comment")
101
        return token;
102
      // When a meaningful token is found and the lexical scope's
103
      // align is undefined, it is an aligned scope.
104
      if (!("align" in lexical))
105
        lexical.align = true;
106
107
      // Execute actions until one 'consumes' the token and we can
108
      // return it.
109
      while(true) {
110
        consume = marked = false;
111
        // Take and execute the topmost action.
112
        cc.pop()(token.type, token.content);
113
        if (consume){
114
          // Marked is used to change the style of the current token.
115
          if (marked)
116
            token.style = marked;
117
          // Here we differentiate between local and global variables.
118
          else if (token.type == "variable" && inScope(token.content))
119
            token.style = "js-localvariable";
120
          return token;
121
        }
122
      }
123
    }
124
125
    // This makes a copy of the parser state. It stores all the
126
    // stateful variables in a closure, and returns a function that
127
    // will restore them when called with a new input stream. Note
128
    // that the cc array has to be copied, because it is contantly
129
    // being modified. Lexical objects are not mutated, and context
130
    // objects are not mutated in a harmful way, so they can be shared
131
    // between runs of the parser.
132
    function copy(){
133
      var _context = context, _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state;
134
135
      return function copyParser(input){
136
        context = _context;
137
        lexical = _lexical;
138
        cc = _cc.concat([]); // copies the array
139
        column = indented = 0;
140
        tokens = tokenizeJavaScript(input, _tokenState);
141
        return parser;
142
      };
143
    }
144
145
    // Helper function for pushing a number of actions onto the cc
146
    // stack in reverse order.
147
    function push(fs){
148
      for (var i = fs.length - 1; i >= 0; i--)
149
        cc.push(fs[i]);
150
    }
151
    // cont and pass are used by the action functions to add other
152
    // actions to the stack. cont will cause the current token to be
153
    // consumed, pass will leave it for the next action.
154
    function cont(){
155
      push(arguments);
156
      consume = true;
157
    }
158
    function pass(){
159
      push(arguments);
160
      consume = false;
161
    }
162
    // Used to change the style of the current token.
163
    function mark(style){
164
      marked = style;
165
    }
166
167
    // Push a new scope. Will automatically link the current scope.
168
    function pushcontext(){
169
      context = {prev: context, vars: {"this": true, "arguments": true}};
170
    }
171
    // Pop off the current scope.
172
    function popcontext(){
173
      context = context.prev;
174
    }
175
    // Register a variable in the current scope.
176
    function register(varname){
177
      if (context){
178
        mark("js-variabledef");
179
        context.vars[varname] = true;
180
      }
181
    }
182
    // Check whether a variable is defined in the current scope.
183
    function inScope(varname){
184
      var cursor = context;
185
      while (cursor) {
186
        if (cursor.vars[varname])
187
          return true;
188
        cursor = cursor.prev;
189
      }
190
      return false;
191
    }
192
193
    // Push a new lexical context of the given type.
194
    function pushlex(type){
195
      var result = function(){
196
        lexical = new JSLexical(indented, column, type, null, lexical)
197
      };
198
      result.lex = true;
199
      return result;
200
    }
201
    // Pop off the current lexical context.
202
    function poplex(){
203
      lexical = lexical.prev;
204
    }
205
    poplex.lex = true;
206
    // The 'lex' flag on these actions is used by the 'next' function
207
    // to know they can (and have to) be ran before moving on to the
208
    // next token.
209
210
    // Creates an action that discards tokens until it finds one of
211
    // the given type.
212
    function expect(wanted){
213
      return function expecting(type){
214
        if (type == wanted) cont();
215
        else cont(arguments.callee);
216
      };
217
    }
218
219
    // Looks for a statement, and then calls itself.
220
    function statements(type){
221
      return pass(statement, statements);
222
    }
223
    // Dispatches various types of statements based on the type of the
224
    // current token.
225
    function statement(type){
226
      if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex);
227
      else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex);
228
      else if (type == "keyword b") cont(pushlex("form"), statement, poplex);
229
      else if (type == "{") cont(pushlex("}"), block, poplex);
230
      else if (type == "function") cont(functiondef);
231
      else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex);
232
      else if (type == "variable") cont(pushlex("stat"), maybelabel);
233
      else if (type == "case") cont(expression, expect(":"));
234
      else if (type == "default") cont(expect(":"));
235
      else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext);
236
      else pass(pushlex("stat"), expression, expect(";"), poplex);
237
    }
238
    // Dispatch expression types.
239
    function expression(type){
240
      if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator);
241
      else if (type == "function") cont(functiondef);
242
      else if (type == "keyword c") cont(expression);
243
      else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex);
244
      else if (type == "operator") cont(expression);
245
      else if (type == "[") cont(pushlex("]"), commasep(expression), expect("]"), poplex);
246
      else if (type == "{") cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
247
    }
248
    // Called for places where operators, function calls, or
249
    // subscripts are valid. Will skip on to the next action if none
250
    // is found.
251
    function maybeoperator(type){
252
      if (type == "operator") cont(expression);
253
      else if (type == "(") cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex, maybeoperator);
254
      else if (type == ".") cont(property, maybeoperator);
255
      else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
256
    }
257
    // When a statement starts with a variable name, it might be a
258
    // label. If no colon follows, it's a regular statement.
259
    function maybelabel(type){
260
      if (type == ":") cont(poplex, statement);
261
      else pass(maybeoperator, expect(";"), poplex);
262
    }
263
    // Property names need to have their style adjusted -- the
264
    // tokenizer thinks they are variables.
265
    function property(type){
266
      if (type == "variable") {mark("js-property"); cont();}
267
    }
268
    // This parses a property and its value in an object literal.
269
    function objprop(type){
270
      if (type == "variable") mark("js-property");
271
      if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression);
272
    }
273
    // Parses a comma-separated list of the things that are recognized
274
    // by the 'what' argument.
275
    function commasep(what){
276
      function proceed(type) {
277
        if (type == ",") cont(what, proceed);
278
      };
279
      return function commaSeparated() {
280
        pass(what, proceed);
281
      };
282
    }
283
    // Look for statements until a closing brace is found.
284
    function block(type){
285
      if (type == "}") cont();
286
      else pass(statement, block);
287
    }
288
    // Variable definitions are split into two actions -- 1 looks for
289
    // a name or the end of the definition, 2 looks for an '=' sign or
290
    // a comma.
291
    function vardef1(type, value){
292
      if (type == "variable"){register(value); cont(vardef2);}
293
      else cont();
294
    }
295
    function vardef2(type){
296
      if (type == "operator") cont(expression, vardef2);
297
      else if (type == ",") cont(vardef1);
298
    }
299
    // For loops.
300
    function forspec1(type, value){
301
      if (type == "var") cont(vardef1, forspec2);
302
      else cont(expression, forspec2);
303
    }
304
    function forspec2(type){
305
      if (type == ",") cont(forspec1);
306
      if (type == ";") cont(expression, expect(";"), expression);
307
    }
308
    // A function definition creates a new context, and the variables
309
    // in its argument list have to be added to this context.
310
    function functiondef(type, value){
311
      if (type == "variable"){register(value); cont(functiondef);}
312
      else if (type == "(") cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
313
    }
314
    function funarg(type, value){
315
      if (type == "variable"){register(value); cont();}
316
    }
317
318
    return parser;
319
  }
320
321
  return {make: parseJS, electricChars: "{}"};
322
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsemarc.js (+102 lines)
Line 0 Link Here
1
Editor.Parser = (function() {
2
	function isWhiteSpace(ch) {
3
		// The messy regexp is because IE's regexp matcher is of the
4
		// opinion that non-breaking spaces are no whitespace.
5
		return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
6
	}
7
8
	var tokenizeMARC = (function() {
9
		function normal(source, setState) {
10
			var ch = source.next();
11
			if (ch == '$' || ch == '|') {
12
				if (source.applies(matcher(/[a-z0-9]/)) && source.next() && source.applies(isWhiteSpace)) {
13
					return 'marc-subfield';
14
				} else {
15
					return 'marc-word';
16
				}
17
			} else if (ch.match(/[0-9]/)) {
18
				// This and the next block are muddled because tags are ^[0-9]{3} and indicators are [0-9_]{2}.
19
				var length = 1;
20
				while (source.applies(matcher(/[0-9]/))) {
21
					source.next();
22
					length++;
23
				}
24
25
				if (length == 1 && source.lookAhead('_')) {
26
					source.next();
27
					return 'marc-indicator';
28
				}
29
30
				if (source.applies(isWhiteSpace) && length == 2) {
31
					return 'marc-indicator';
32
				} else if (source.applies(isWhiteSpace) && length == 3) {
33
					return 'marc-tag';
34
				} else {
35
					return 'marc-word';
36
				}
37
			} else if (ch == '_') {
38
				if (source.applies(matcher(/[0-9_]/)) && source.next() && source.applies(isWhiteSpace)) {
39
					return 'marc-indicator';
40
				} else {
41
					return 'marc-word';
42
				}
43
			} else {
44
				source.nextWhile(matcher(/[^\$|\n]/));
45
				return 'marc-word';
46
			}
47
		}
48
49
		return function(source, startState) {
50
			return tokenizer(source, startState || normal);
51
		};
52
	})();
53
54
	function indentMARC(context) {
55
		return function(nextChars) {
56
			return 0;
57
		};
58
	}
59
60
	function parseMARC(source) {
61
		var tokens = tokenizeMARC(source);
62
		var context = null, indent = 0, col = 0;
63
64
		var iter = {
65
			next: function() {
66
				var token = tokens.next(), type = token.style, content = token.content, width = token.value.length;
67
68
				if (content == "\n") {
69
					token.indentation = indentMARC(context);
70
					indent = col = 0;
71
					if (context && context.align === null) { context.align = false }
72
				} else if (type == "whitespace" && col === 0) {
73
					indent = width;
74
				} else if (type != "sp-comment" && context && context.align === null) {
75
					context.align = true;
76
				}
77
78
				if ((type == 'marc-tag' && col != 0) || (type == 'marc-indicator' && col != 4)) {
79
					token.style = 'marc-word';
80
				}
81
82
				if (content != "\n") { col += width }
83
84
				return token;
85
			},
86
87
			copy: function() {
88
				var _context = context, _indent = indent, _col = col, _tokenState = tokens.state;
89
				return function(source) {
90
					tokens = tokenizeMARC(source, _tokenState);
91
					context = _context;
92
					indent = _indent;
93
					col = _col;
94
					return iter;
95
				};
96
			}
97
		};
98
		return iter;
99
	}
100
101
	return {make: parseMARC, electricChars: "}]"};
102
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsesparql.js (+162 lines)
Line 0 Link Here
1
Editor.Parser = (function() {
2
  function wordRegexp(words) {
3
    return new RegExp("^(?:" + words.join("|") + ")$", "i");
4
  }
5
  var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
6
                        "isblank", "isliteral", "union", "a"]);
7
  var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe",
8
                             "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional",
9
                             "graph", "by", "asc", "desc", ]);
10
  var operatorChars = /[*+\-<>=&|]/;
11
12
  var tokenizeSparql = (function() {
13
    function normal(source, setState) {
14
      var ch = source.next();
15
      if (ch == "$" || ch == "?") {
16
        source.nextWhile(matcher(/[\w\d]/));
17
        return "sp-var";
18
      }
19
      else if (ch == "<" && !source.applies(matcher(/[\s\u00a0=]/))) {
20
        source.nextWhile(matcher(/[^\s\u00a0>]/));
21
        if (source.equals(">")) source.next();
22
        return "sp-uri";
23
      }
24
      else if (ch == "\"" || ch == "'") {
25
        setState(inLiteral(ch));
26
        return null;
27
      }
28
      else if (/[{}\(\),\.;\[\]]/.test(ch)) {
29
        return "sp-punc";
30
      }
31
      else if (ch == "#") {
32
        while (!source.endOfLine()) source.next();
33
        return "sp-comment";
34
      }
35
      else if (operatorChars.test(ch)) {
36
        source.nextWhile(matcher(operatorChars));
37
        return "sp-operator";
38
      }
39
      else if (ch == ":") {
40
        source.nextWhile(matcher(/[\w\d\._\-]/));
41
        return "sp-prefixed";
42
      }
43
      else {
44
        source.nextWhile(matcher(/[_\w\d]/));
45
        if (source.equals(":")) {
46
          source.next();
47
          source.nextWhile(matcher(/[\w\d_\-]/));
48
          return "sp-prefixed";
49
        }
50
        var word = source.get(), type;
51
        if (ops.test(word))
52
          type = "sp-operator";
53
        else if (keywords.test(word))
54
          type = "sp-keyword";
55
        else
56
          type = "sp-word";
57
        return {style: type, content: word};
58
      }
59
    }
60
61
    function inLiteral(quote) {
62
      return function(source, setState) {
63
        var escaped = false;
64
        while (!source.endOfLine()) {
65
          var ch = source.next();
66
          if (ch == quote && !escaped) {
67
            setState(normal);
68
            break;
69
          }
70
          escaped = !escaped && ch == "\\";
71
        }
72
        return "sp-literal";
73
      };
74
    }
75
76
    return function(source, startState) {
77
      return tokenizer(source, startState || normal);
78
    };
79
  })();
80
81
  function indentSparql(context) {
82
    return function(nextChars) {
83
      var firstChar = nextChars && nextChars.charAt(0);
84
      if (/[\]\}]/.test(firstChar))
85
        while (context && context.type == "pattern") context = context.prev;
86
87
      var closing = context && firstChar == matching[context.type];
88
      if (!context)
89
        return 0;
90
      else if (context.type == "pattern")
91
        return context.col;
92
      else if (context.align)
93
        return context.col - (closing ? context.width : 0);
94
      else
95
        return context.indent + (closing ? 0 : 2);
96
    }
97
  }
98
99
  function parseSparql(source) {
100
    var tokens = tokenizeSparql(source);
101
    var context = null, indent = 0, col = 0;
102
    function pushContext(type, width) {
103
      context = {prev: context, indent: indent, col: col, type: type, width: width};
104
    }
105
    function popContext() {
106
      context = context.prev;
107
    }
108
109
    var iter = {
110
      next: function() {
111
        var token = tokens.next(), type = token.style, content = token.content, width = token.value.length;
112
113
        if (content == "\n") {
114
          token.indentation = indentSparql(context);
115
          indent = col = 0;
116
          if (context && context.align == null) context.align = false;
117
        }
118
        else if (type == "whitespace" && col == 0) {
119
          indent = width;
120
        }
121
        else if (type != "sp-comment" && context && context.align == null) {
122
          context.align = true;
123
        }
124
125
        if (content != "\n") col += width;
126
127
        if (/[\[\{\(]/.test(content)) {
128
          pushContext(content, width);
129
        }
130
        else if (/[\]\}\)]/.test(content)) {
131
          while (context && context.type == "pattern")
132
            popContext();
133
          if (context && content == matching[context.type])
134
            popContext();
135
        }
136
        else if (content == "." && context && context.type == "pattern") {
137
          popContext();
138
        }
139
        else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") &&
140
                 context && /[\{\[]/.test(context.type)) {
141
          pushContext("pattern", width);
142
        }
143
144
        return token;
145
      },
146
147
      copy: function() {
148
        var _context = context, _indent = indent, _col = col, _tokenState = tokens.state;
149
        return function(source) {
150
          tokens = tokenizeSparql(source, _tokenState);
151
          context = _context;
152
          indent = _indent;
153
          col = _col;
154
          return iter;
155
        };
156
      }
157
    };
158
    return iter;
159
  }
160
161
  return {make: parseSparql, electricChars: "}]"};
162
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/parsexml.js (+286 lines)
Line 0 Link Here
1
/* This file defines an XML parser, with a few kludges to make it
2
 * useable for HTML. autoSelfClosers defines a set of tag names that
3
 * are expected to not have a closing tag, and doNotIndent specifies
4
 * the tags inside of which no indentation should happen (see Config
5
 * object). These can be disabled by passing the editor an object like
6
 * {useHTMLKludges: false} as parserConfig option.
7
 */
8
9
var XMLParser = Editor.Parser = (function() {
10
  var Kludges = {
11
    autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
12
                      "meta": true, "col": true, "frame": true, "base": true, "area": true},
13
    doNotIndent: {"pre": true}
14
  };
15
  var NoKludges = {autoSelfClosers: {}, doNotIndent: {}};
16
  var UseKludges = Kludges;
17
18
  // Simple stateful tokenizer for XML documents. Returns a
19
  // MochiKit-style iterator, with a state property that contains a
20
  // function encapsulating the current state. See tokenize.js.
21
  var tokenizeXML = (function() {
22
    function inText(source, setState) {
23
      var ch = source.next();
24
      if (ch == "<") {
25
        if (source.equals("!")) {
26
          source.next();
27
          if (source.equals("[")) {
28
            if (source.lookAhead("[CDATA[", true)) {
29
              setState(inBlock("xml-cdata", "]]>"));
30
              return null;
31
            }
32
            else {
33
              return "xml-text";
34
            }
35
          }
36
          else if (source.lookAhead("--", true)) {
37
            setState(inBlock("xml-comment", "-->"));
38
            return null;
39
          }
40
          else {
41
            return "xml-text";
42
          }
43
        }
44
        else if (source.equals("?")) {
45
          source.next();
46
          source.nextWhile(matcher(/[\w\._\-]/));
47
          setState(inBlock("xml-processing", "?>"));
48
          return "xml-processing";
49
        }
50
        else {
51
          if (source.equals("/")) source.next();
52
          setState(inTag);
53
          return "xml-punctuation";
54
        }
55
      }
56
      else if (ch == "&") {
57
        while (!source.endOfLine()) {
58
          if (source.next() == ";")
59
            break;
60
        }
61
        return "xml-entity";
62
      }
63
      else {
64
        source.nextWhile(matcher(/[^&<\n]/));
65
        return "xml-text";
66
      }
67
    }
68
69
    function inTag(source, setState) {
70
      var ch = source.next();
71
      if (ch == ">") {
72
        setState(inText);
73
        return "xml-punctuation";
74
      }
75
      else if (/[?\/]/.test(ch) && source.equals(">")) {
76
        source.next();
77
        setState(inText);
78
        return "xml-punctuation";
79
      }
80
      else if (ch == "=") {
81
        return "xml-punctuation";
82
      }
83
      else if (/[\'\"]/.test(ch)) {
84
        setState(inAttribute(ch));
85
        return null;
86
      }
87
      else {
88
        source.nextWhile(matcher(/[^\s\u00a0=<>\"\'\/?]/));
89
        return "xml-name";
90
      }
91
    }
92
93
    function inAttribute(quote) {
94
      return function(source, setState) {
95
        while (!source.endOfLine()) {
96
          if (source.next() == quote) {
97
            setState(inTag);
98
            break;
99
          }
100
        }
101
        return "xml-attribute";
102
      };
103
    }
104
105
    function inBlock(style, terminator) {
106
      return function(source, setState) {
107
        while (!source.endOfLine()) {
108
          if (source.lookAhead(terminator, true)) {
109
            setState(inText);
110
            break;
111
          }
112
          source.next();
113
        }
114
        return style;
115
      };
116
    }
117
118
    return function(source, startState) {
119
      return tokenizer(source, startState || inText);
120
    };
121
  })();
122
123
  // The parser. The structure of this function largely follows that of
124
  // parseJavaScript in parsejavascript.js (there is actually a bit more
125
  // shared code than I'd like), but it is quite a bit simpler.
126
  function parseXML(source) {
127
    var tokens = tokenizeXML(source);
128
    var cc = [base];
129
    var tokenNr = 0, indented = 0;
130
    var currentTag = null, context = null;
131
    var consume, marked;
132
133
    function push(fs) {
134
      for (var i = fs.length - 1; i >= 0; i--)
135
        cc.push(fs[i]);
136
    }
137
    function cont() {
138
      push(arguments);
139
      consume = true;
140
    }
141
    function pass() {
142
      push(arguments);
143
      consume = false;
144
    }
145
146
    function mark(style) {
147
      marked = style;
148
    }
149
    function expect(text) {
150
      return function(style, content) {
151
        if (content == text) cont();
152
        else mark("xml-error") || cont(arguments.callee);
153
      };
154
    }
155
156
    function pushContext(tagname, startOfLine) {
157
      var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
158
      context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
159
    }
160
    function popContext() {
161
      context = context.prev;
162
    }
163
    function computeIndentation(baseContext) {
164
      return function(nextChars) {
165
        var context = baseContext;
166
        if (context && context.noIndent)
167
          return 0;
168
        if (context && /^<\//.test(nextChars))
169
          context = context.prev;
170
        while (context && !context.startOfLine)
171
          context = context.prev;
172
        if (context)
173
          return context.indent + 2;
174
        else
175
          return 0;
176
      };
177
    }
178
179
    function base() {
180
      return pass(element, base);
181
    }
182
    var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true,
183
                          "xml-cdata": true, "xml-processing": true};
184
    function element(style, content) {
185
      if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
186
      else if (content == "</") cont(closetagname, expect(">"));
187
      else if (content == "<?") cont(tagname, attributes, expect("?>"));
188
      else if (harmlessTokens.hasOwnProperty(style)) cont();
189
      else mark("xml-error") || cont();
190
    }
191
    function tagname(style, content) {
192
      if (style == "xml-name") {
193
        currentTag = content.toLowerCase();
194
        mark("xml-tagname");
195
        cont();
196
      }
197
      else {
198
        currentTag = null;
199
        pass();
200
      }
201
    }
202
    function closetagname(style, content) {
203
      if (style == "xml-name" && context && content.toLowerCase() == context.name) {
204
        popContext();
205
        mark("xml-tagname");
206
      }
207
      else {
208
        mark("xml-error");
209
      }
210
      cont();
211
    }
212
    function endtag(startOfLine) {
213
      return function(style, content) {
214
        if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
215
        else if (content == ">") pushContext(currentTag, startOfLine) || cont();
216
        else mark("xml-error") || cont(arguments.callee);
217
      };
218
    }
219
    function attributes(style) {
220
      if (style == "xml-name") mark("xml-attname") || cont(attribute, attributes);
221
      else pass();
222
    }
223
    function attribute(style, content) {
224
      if (content == "=") cont(value);
225
      else if (content == ">" || content == "/>") pass(endtag);
226
      else pass();
227
    }
228
    function value(style) {
229
      if (style == "xml-attribute") cont(value);
230
      else pass();
231
    }
232
233
    return {
234
      indentation: function() {return indented;},
235
236
      next: function(){
237
        var token = tokens.next();
238
        if (token.style == "whitespace" && tokenNr == 0)
239
          indented = token.value.length;
240
        else
241
          tokenNr++;
242
        if (token.content == "\n") {
243
          indented = tokenNr = 0;
244
          token.indentation = computeIndentation(context);
245
        }
246
247
        if (token.style == "whitespace" || token.type == "xml-comment")
248
          return token;
249
250
        while(true){
251
          consume = marked = false;
252
          cc.pop()(token.style, token.content);
253
          if (consume){
254
            if (marked)
255
              token.style = marked;
256
            return token;
257
          }
258
        }
259
      },
260
261
      copy: function(){
262
        var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
263
        var parser = this;
264
265
        return function(input){
266
          cc = _cc.concat([]);
267
          tokenNr = indented = 0;
268
          context = _context;
269
          tokens = tokenizeXML(input, _tokenState);
270
          return parser;
271
        };
272
      }
273
    };
274
  }
275
276
  return {
277
    make: parseXML,
278
    electricChars: "/",
279
    configure: function(config) {
280
      if (config.useHTMLKludges)
281
        UseKludges = Kludges;
282
      else
283
        UseKludges = NoKludges;
284
    }
285
  };
286
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/select.js (+584 lines)
Line 0 Link Here
1
/* Functionality for finding, storing, and restoring selections
2
 *
3
 * This does not provide a generic API, just the minimal functionality
4
 * required by the CodeMirror system.
5
 */
6
7
// Namespace object.
8
var select = {};
9
10
(function() {
11
  select.ie_selection = document.selection && document.selection.createRangeCollection;
12
13
  // Find the 'top-level' (defined as 'a direct child of the node
14
  // passed as the top argument') node that the given node is
15
  // contained in. Return null if the given node is not inside the top
16
  // node.
17
  function topLevelNodeAt(node, top) {
18
    while (node && node.parentNode != top)
19
      node = node.parentNode;
20
    return node;
21
  }
22
23
  // Find the top-level node that contains the node before this one.
24
  function topLevelNodeBefore(node, top) {
25
    while (!node.previousSibling && node.parentNode != top)
26
      node = node.parentNode;
27
    return topLevelNodeAt(node.previousSibling, top);
28
  }
29
30
  // Used to prevent restoring a selection when we do not need to.
31
  var currentSelection = null;
32
33
  var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
34
35
  select.snapshotChanged = function() {
36
    if (currentSelection) currentSelection.changed = true;
37
  };
38
39
  // This is called by the code in editor.js whenever it is replacing
40
  // a text node. The function sees whether the given oldNode is part
41
  // of the current selection, and updates this selection if it is.
42
  // Because nodes are often only partially replaced, the length of
43
  // the part that gets replaced has to be taken into account -- the
44
  // selection might stay in the oldNode if the newNode is smaller
45
  // than the selection's offset. The offset argument is needed in
46
  // case the selection does move to the new object, and the given
47
  // length is not the whole length of the new node (part of it might
48
  // have been used to replace another node).
49
  select.snapshotReplaceNode = function(from, to, length, offset) {
50
    if (!currentSelection) return;
51
    currentSelection.changed = true;
52
53
    function replace(point) {
54
      if (from == point.node) {
55
        if (length && point.offset > length) {
56
          point.offset -= length;
57
        }
58
        else {
59
          point.node = to;
60
          point.offset += (offset || 0);
61
        }
62
      }
63
    }
64
    replace(currentSelection.start);
65
    replace(currentSelection.end);
66
  };
67
68
  select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
69
    if (!currentSelection) return;
70
    currentSelection.changed = true;
71
72
    function move(point) {
73
      if (from == point.node && (!ifAtStart || point.offset == 0)) {
74
        point.node = to;
75
        if (relative) point.offset = Math.max(0, point.offset + distance);
76
        else point.offset = distance;
77
      }
78
    }
79
    move(currentSelection.start);
80
    move(currentSelection.end);
81
  };
82
83
  // Most functions are defined in two ways, one for the IE selection
84
  // model, one for the W3C one.
85
  if (select.ie_selection) {
86
    function selectionNode(win, start) {
87
      var range = win.document.selection.createRange();
88
      range.collapse(start);
89
90
      function nodeAfter(node) {
91
        var found = null;
92
        while (!found && node) {
93
          found = node.nextSibling;
94
          node = node.parentNode;
95
        }
96
        return nodeAtStartOf(found);
97
      }
98
99
      function nodeAtStartOf(node) {
100
        while (node && node.firstChild) node = node.firstChild;
101
        return {node: node, offset: 0};
102
      }
103
104
      var containing = range.parentElement();
105
      if (!isAncestor(win.document.body, containing)) return null;
106
      if (!containing.firstChild) return nodeAtStartOf(containing);
107
108
      var working = range.duplicate();
109
      working.moveToElementText(containing);
110
      working.collapse(true);
111
      for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
112
        if (cur.nodeType == 3) {
113
          var size = cur.nodeValue.length;
114
          working.move("character", size);
115
        }
116
        else {
117
          working.moveToElementText(cur);
118
          working.collapse(false);
119
        }
120
121
        var dir = range.compareEndPoints("StartToStart", working);
122
        if (dir == 0) return nodeAfter(cur);
123
        if (dir == 1) continue;
124
        if (cur.nodeType != 3) return nodeAtStartOf(cur);
125
126
        working.setEndPoint("StartToEnd", range);
127
        return {node: cur, offset: size - working.text.length};
128
      }
129
      return nodeAfter(containing);
130
    }
131
132
    select.markSelection = function(win) {
133
      currentSelection = null;
134
      var sel = win.document.selection;
135
      if (!sel) return;
136
      var start = selectionNode(win, true),
137
          end = sel.createRange().text == "" ? start : selectionNode(win, false);
138
      if (!start || !end) return;
139
      currentSelection = {start: start, end: end, window: win, changed: false};
140
    };
141
142
    select.selectMarked = function() {
143
      if (!currentSelection || !currentSelection.changed) return;
144
145
      function makeRange(point) {
146
        var range = currentSelection.window.document.body.createTextRange();
147
        var node = point.node;
148
        if (!node) {
149
          range.moveToElementText(win.document.body);
150
          range.collapse(false);
151
        }
152
        else if (node.nodeType == 3) {
153
          range.moveToElementText(node.parentNode);
154
          var offset = point.offset;
155
          while (node.previousSibling) {
156
            node = node.previousSibling;
157
            offset += (node.innerText || "").length;
158
          }
159
          range.move("character", offset);
160
        }
161
        else {
162
          range.moveToElementText(node);
163
          range.collapse(true);
164
        }
165
        return range;
166
      }
167
168
      var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
169
      start.setEndPoint("StartToEnd", end);
170
      start.select();
171
    };
172
173
    // Get the top-level node that one end of the cursor is inside or
174
    // after. Note that this returns false for 'no cursor', and null
175
    // for 'start of document'.
176
    select.selectionTopNode = function(container, start) {
177
      var selection = container.ownerDocument.selection;
178
      if (!selection) return false;
179
180
      var range = selection.createRange();
181
      range.collapse(start);
182
      var around = range.parentElement();
183
      if (around && isAncestor(container, around)) {
184
        // Only use this node if the selection is not at its start.
185
        var range2 = range.duplicate();
186
        range2.moveToElementText(around);
187
        if (range.compareEndPoints("StartToStart", range2) == -1)
188
          return topLevelNodeAt(around, container);
189
      }
190
      // Fall-back hack
191
      try {range.pasteHTML("<span id='xxx-temp-xxx'></span>");}
192
      catch (e) {return false;}
193
194
      var temp = container.ownerDocument.getElementById("xxx-temp-xxx");
195
      if (temp) {
196
        var result = topLevelNodeBefore(temp, container);
197
        removeElement(temp);
198
        return result;
199
      }
200
      return false;
201
    };
202
203
    // Place the cursor after this.start. This is only useful when
204
    // manually moving the cursor instead of restoring it to its old
205
    // position.
206
    select.focusAfterNode = function(node, container) {
207
      var range = container.ownerDocument.body.createTextRange();
208
      range.moveToElementText(node || container);
209
      range.collapse(!node);
210
      range.select();
211
    };
212
213
    select.somethingSelected = function(win) {
214
      var sel = win.document.selection;
215
      return sel && (sel.createRange().text != "");
216
    };
217
218
    function insertAtCursor(window, html) {
219
      var selection = window.document.selection;
220
      if (selection) {
221
        var range = selection.createRange();
222
        range.pasteHTML(html);
223
        range.collapse(false);
224
        range.select();
225
      }
226
    }
227
228
    // Used to normalize the effect of the enter key, since browsers
229
    // do widely different things when pressing enter in designMode.
230
    select.insertNewlineAtCursor = function(window) {
231
      insertAtCursor(window, "<br/>");
232
    };
233
234
    select.insertTabAtCursor = function(window) {
235
      insertAtCursor(window, fourSpaces);
236
    };
237
238
    // Get the BR node at the start of the line on which the cursor
239
    // currently is, and the offset into the line. Returns null as
240
    // node if cursor is on first line.
241
    select.cursorPos = function(container, start) {
242
      var selection = container.ownerDocument.selection;
243
      if (!selection) return null;
244
245
      var topNode = select.selectionTopNode(container, start);
246
      while (topNode && topNode.nodeName != "BR")
247
        topNode = topNode.previousSibling;
248
249
      var range = selection.createRange(), range2 = range.duplicate();
250
      range.collapse(start);
251
      if (topNode) {
252
        range2.moveToElementText(topNode);
253
        range2.collapse(false);
254
      }
255
      else {
256
        // When nothing is selected, we can get all kinds of funky errors here.
257
        try { range2.moveToElementText(container); }
258
        catch (e) { return null; }
259
        range2.collapse(true);
260
      }
261
      range.setEndPoint("StartToStart", range2);
262
263
      return {node: topNode, offset: range.text.length};
264
    };
265
266
    select.setCursorPos = function(container, from, to) {
267
      function rangeAt(pos) {
268
        var range = container.ownerDocument.body.createTextRange();
269
        if (!pos.node) {
270
          range.moveToElementText(container);
271
          range.collapse(true);
272
        }
273
        else {
274
          range.moveToElementText(pos.node);
275
          range.collapse(false);
276
        }
277
        range.move("character", pos.offset);
278
        return range;
279
      }
280
281
      var range = rangeAt(from);
282
      if (to && to != from)
283
        range.setEndPoint("EndToEnd", rangeAt(to));
284
      range.select();
285
    }
286
287
    // Make sure the cursor is visible.
288
    select.scrollToCursor = function(container) {
289
      var selection = container.ownerDocument.selection;
290
      if (!selection) return null;
291
      selection.createRange().scrollIntoView();
292
    };
293
294
    // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
295
    select.selectionCoords = function (win) {
296
      var selection = win.document.selection;
297
      if (!selection) return null;
298
      var start = selection.createRange(), end = start.duplicate();
299
      start.collapse(true);
300
      end.collapse(false);
301
302
      var body = win.document.body;
303
      return {start: {x: start.boundingLeft + body.scrollLeft - 1,
304
                      y: start.boundingTop + body.scrollTop},
305
              end: {x: end.boundingLeft + body.scrollLeft - 1,
306
                    y: end.boundingTop + body.scrollTop}};
307
    };
308
309
    // Restore a stored selection.
310
    select.selectCoords = function(win, coords) {
311
      if (!coords) return;
312
313
      var range1 = win.document.body.createTextRange(), range2 = range1.duplicate();
314
      // This can fail for various hard-to-handle reasons.
315
      try {
316
        range1.moveToPoint(coords.start.x, coords.start.y);
317
        range2.moveToPoint(coords.end.x, coords.end.y);
318
        range1.setEndPoint("EndToStart", range2);
319
        range1.select();
320
      } catch(e) {alert(e.message);}
321
    };
322
  }
323
  // W3C model
324
  else {
325
    // This is used to fix an issue with getting the scroll position
326
    // in Opera.
327
    var opera_scroll = window.scrollX == null;
328
329
    // Store start and end nodes, and offsets within these, and refer
330
    // back to the selection object from those nodes, so that this
331
    // object can be updated when the nodes are replaced before the
332
    // selection is restored.
333
    select.markSelection = function (win) {
334
      var selection = win.getSelection();
335
      if (!selection || selection.rangeCount == 0)
336
        return (currentSelection = null);
337
      var range = selection.getRangeAt(0);
338
339
      currentSelection = {
340
        start: {node: range.startContainer, offset: range.startOffset},
341
        end: {node: range.endContainer, offset: range.endOffset},
342
        window: win,
343
        scrollX: opera_scroll && win.document.body.scrollLeft,
344
        scrollY: opera_scroll && win.document.body.scrollTop,
345
        changed: false
346
      };
347
348
      // We want the nodes right at the cursor, not one of their
349
      // ancestors with a suitable offset. This goes down the DOM tree
350
      // until a 'leaf' is reached (or is it *up* the DOM tree?).
351
      function normalize(point){
352
        while (point.node.nodeType != 3 && point.node.nodeName != "BR") {
353
          var newNode = point.node.childNodes[point.offset] || point.node.nextSibling;
354
          point.offset = 0;
355
          while (!newNode && point.node.parentNode) {
356
            point.node = point.node.parentNode;
357
            newNode = point.node.nextSibling;
358
          }
359
          point.node = newNode;
360
          if (!newNode)
361
            break;
362
        }
363
      }
364
365
      normalize(currentSelection.start);
366
      normalize(currentSelection.end);
367
    };
368
369
    select.selectMarked = function () {
370
      if (!currentSelection || !currentSelection.changed) return;
371
      var win = currentSelection.window, range = win.document.createRange();
372
373
      function setPoint(point, which) {
374
        if (point.node) {
375
          // Some magic to generalize the setting of the start and end
376
          // of a range.
377
          if (point.offset == 0)
378
            range["set" + which + "Before"](point.node);
379
          else
380
            range["set" + which](point.node, point.offset);
381
        }
382
        else {
383
          range.setStartAfter(win.document.body.lastChild || win.document.body);
384
        }
385
      }
386
387
      // Have to restore the scroll position of the frame in Opera.
388
      if (opera_scroll) {
389
        win.document.body.scrollLeft = currentSelection.scrollX;
390
        win.document.body.scrollTop = currentSelection.scrollY;
391
      }
392
      setPoint(currentSelection.end, "End");
393
      setPoint(currentSelection.start, "Start");
394
      selectRange(range, win);
395
    };
396
397
    // Helper for selecting a range object.
398
    function selectRange(range, window) {
399
      var selection = window.getSelection();
400
      selection.removeAllRanges();
401
      selection.addRange(range);
402
    };
403
    function selectionRange(window) {
404
      var selection = window.getSelection();
405
      if (!selection || selection.rangeCount == 0)
406
        return false;
407
      else
408
        return selection.getRangeAt(0);
409
    }
410
411
    // Finding the top-level node at the cursor in the W3C is, as you
412
    // can see, quite an involved process.
413
    select.selectionTopNode = function(container, start) {
414
      var range = selectionRange(container.ownerDocument.defaultView);
415
      if (!range) return false;
416
417
      var node = start ? range.startContainer : range.endContainer;
418
      var offset = start ? range.startOffset : range.endOffset;
419
      // Work around (yet another) bug in Opera's selection model.
420
      if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
421
          container.childNodes[range.startOffset] && container.childNodes[range.startOffset].nodeName == "BR")
422
        offset--;
423
424
      // For text nodes, we look at the node itself if the cursor is
425
      // inside, or at the node before it if the cursor is at the
426
      // start.
427
      if (node.nodeType == 3){
428
        if (offset > 0)
429
          return topLevelNodeAt(node, container);
430
        else
431
          return topLevelNodeBefore(node, container);
432
      }
433
      // Occasionally, browsers will return the HTML node as
434
      // selection. If the offset is 0, we take the start of the frame
435
      // ('after null'), otherwise, we take the last node.
436
      else if (node.nodeName == "HTML") {
437
        return (offset == 1 ? null : container.lastChild);
438
      }
439
      // If the given node is our 'container', we just look up the
440
      // correct node by using the offset.
441
      else if (node == container) {
442
        return (offset == 0) ? null : node.childNodes[offset - 1];
443
      }
444
      // In any other case, we have a regular node. If the cursor is
445
      // at the end of the node, we use the node itself, if it is at
446
      // the start, we use the node before it, and in any other
447
      // case, we look up the child before the cursor and use that.
448
      else {
449
        if (offset == node.childNodes.length)
450
          return topLevelNodeAt(node, container);
451
        else if (offset == 0)
452
          return topLevelNodeBefore(node, container);
453
        else
454
          return topLevelNodeAt(node.childNodes[offset - 1], container);
455
      }
456
    };
457
458
    select.focusAfterNode = function(node, container) {
459
      var win = container.ownerDocument.defaultView,
460
          range = win.document.createRange();
461
      range.setStartBefore(container.firstChild || container);
462
      // In Opera, setting the end of a range at the end of a line
463
      // (before a BR) will cause the cursor to appear on the next
464
      // line, so we set the end inside of the start node when
465
      // possible.
466
      if (node && !node.firstChild)
467
        range.setEndAfter(node);
468
      else if (node)
469
        range.setEnd(node, node.childNodes.length);
470
      else
471
        range.setEndBefore(container.firstChild || container);
472
      range.collapse(false);
473
      selectRange(range, win);
474
    };
475
476
    select.somethingSelected = function(win) {
477
      var range = selectionRange(win);
478
      return range && !range.collapsed;
479
    };
480
481
    function insertNodeAtCursor(window, node) {
482
      var range = selectionRange(window);
483
      if (!range) return;
484
485
      range.deleteContents();
486
      range.insertNode(node);
487
      range.setEndAfter(node);
488
      range.collapse(false);
489
      selectRange(range, window);
490
      return node;
491
    }
492
493
    select.insertNewlineAtCursor = function(window) {
494
      insertNodeAtCursor(window, window.document.createElement("BR"));
495
    };
496
497
    select.insertTabAtCursor = function(window) {
498
      insertNodeAtCursor(window, window.document.createTextNode(fourSpaces));
499
    };
500
501
    select.cursorPos = function(container, start) {
502
      var range = selectionRange(window);
503
      if (!range) return;
504
505
      var topNode = select.selectionTopNode(container, start);
506
      while (topNode && topNode.nodeName != "BR")
507
        topNode = topNode.previousSibling;
508
509
      range = range.cloneRange();
510
      range.collapse(start);
511
      if (topNode)
512
        range.setStartAfter(topNode);
513
      else
514
        range.setStartBefore(container);
515
      return {node: topNode, offset: range.toString().length};
516
    };
517
518
    select.setCursorPos = function(container, from, to) {
519
      var win = container.ownerDocument.defaultView,
520
          range = win.document.createRange();
521
522
      function setPoint(node, offset, side) {
523
        if (!node)
524
          node = container.firstChild;
525
        else
526
          node = node.nextSibling;
527
528
        if (!node)
529
          return;
530
531
        if (offset == 0) {
532
          range["set" + side + "Before"](node);
533
          return true;
534
        }
535
536
        var backlog = []
537
        function decompose(node) {
538
          if (node.nodeType == 3)
539
            backlog.push(node);
540
          else
541
            forEach(node.childNodes, decompose);
542
        }
543
        while (true) {
544
          while (node && !backlog.length) {
545
            decompose(node);
546
            node = node.nextSibling;
547
          }
548
          var cur = backlog.shift();
549
          if (!cur) return false;
550
551
          var length = cur.nodeValue.length;
552
          if (length >= offset) {
553
            range["set" + side](cur, offset);
554
            return true;
555
          }
556
          offset -= length;
557
        }
558
      }
559
560
      to = to || from;
561
      if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
562
        selectRange(range, win);
563
    };
564
565
    select.scrollToCursor = function(container) {
566
      var body = container.ownerDocument.body, win = container.ownerDocument.defaultView;
567
      var element = select.selectionTopNode(container, true) || container.firstChild;
568
569
      // In Opera, BR elements *always* have a scrollTop property of zero. Go Opera.
570
      while (element && !element.offsetTop)
571
        element = element.previousSibling;
572
573
      var y = 0, pos = element;
574
      while (pos && pos.offsetParent) {
575
        y += pos.offsetTop;
576
        pos = pos.offsetParent;
577
      }
578
579
      var screen_y = y - body.scrollTop;
580
      if (screen_y < 0 || screen_y > win.innerHeight - 10)
581
        win.scrollTo(0, y);
582
    };
583
  }
584
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/stringstream.js (+131 lines)
Line 0 Link Here
1
/* String streams are the things fed to parsers (which can feed them
2
 * to a tokenizer if they want). They provide peek and next methods
3
 * for looking at the current character (next 'consumes' this
4
 * character, peek does not), and a get method for retrieving all the
5
 * text that was consumed since the last time get was called.
6
 *
7
 * An easy mistake to make is to let a StopIteration exception finish
8
 * the token stream while there are still characters pending in the
9
 * string stream (hitting the end of the buffer while parsing a
10
 * token). To make it easier to detect such errors, the strings throw
11
 * an exception when this happens.
12
 */
13
14
// Make a string stream out of an iterator that returns strings. This
15
// is applied to the result of traverseDOM (see codemirror.js), and
16
// the resulting stream is fed to the parser.
17
window.stringStream = function(source){
18
  source = iter(source);
19
  // String that's currently being iterated over.
20
  var current = "";
21
  // Position in that string.
22
  var pos = 0;
23
  // Accumulator for strings that have been iterated over but not
24
  // get()-ed yet.
25
  var accum = "";
26
  // Make sure there are more characters ready, or throw
27
  // StopIteration.
28
  function ensureChars() {
29
    while (pos == current.length) {
30
      accum += current;
31
      current = ""; // In case source.next() throws
32
      pos = 0;
33
      try {current = source.next();}
34
      catch (e) {
35
        if (e != StopIteration) throw e;
36
        else return false;
37
      }
38
    }
39
    return true;
40
  }
41
42
  return {
43
    // Return the next character in the stream.
44
    peek: function() {
45
      if (!ensureChars()) return null;
46
      return current.charAt(pos);
47
    },
48
    // Get the next character, throw StopIteration if at end, check
49
    // for unused content.
50
    next: function() {
51
      if (!ensureChars()) {
52
        if (accum.length > 0)
53
          throw "End of stringstream reached without emptying buffer ('" + accum + "').";
54
        else
55
          throw StopIteration;
56
      }
57
      return current.charAt(pos++);
58
    },
59
    // Return the characters iterated over since the last call to
60
    // .get().
61
    get: function() {
62
      var temp = accum;
63
      accum = "";
64
      if (pos > 0){
65
        temp += current.slice(0, pos);
66
        current = current.slice(pos);
67
        pos = 0;
68
      }
69
      return temp;
70
    },
71
    // Push a string back into the stream.
72
    push: function(str) {
73
      current = current.slice(0, pos) + str + current.slice(pos);
74
    },
75
    lookAhead: function(str, consume, skipSpaces, caseInsensitive) {
76
      function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
77
      str = cased(str);
78
      var found = false;
79
80
      var _accum = accum, _pos = pos;
81
      if (skipSpaces) this.nextWhile(matcher(/[\s\u00a0]/));
82
83
      while (true) {
84
        var end = pos + str.length, left = current.length - pos;
85
        if (end <= current.length) {
86
          found = str == cased(current.slice(pos, end));
87
          pos = end;
88
          break;
89
        }
90
        else if (str.slice(0, left) == cased(current.slice(pos))) {
91
          accum += current; current = "";
92
          try {current = source.next();}
93
          catch (e) {break;}
94
          pos = 0;
95
          str = str.slice(left);
96
        }
97
        else {
98
          break;
99
        }
100
      }
101
102
      if (!(found && consume)) {
103
        current = accum.slice(_accum.length) + current;
104
        pos = _pos;
105
        accum = _accum;
106
      }
107
108
      return found;
109
    },
110
111
    // Utils built on top of the above
112
    more: function() {
113
      return this.peek() !== null;
114
    },
115
    applies: function(test) {
116
      var next = this.peek();
117
      return (next !== null && test(next));
118
    },
119
    nextWhile: function(test) {
120
      while (this.applies(test))
121
        this.next();
122
    },
123
    equals: function(ch) {
124
      return ch === this.peek();
125
    },
126
    endOfLine: function() {
127
      var next = this.peek();
128
      return next == null || next == "\n";
129
    }
130
  };
131
};
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenize.js (+57 lines)
Line 0 Link Here
1
// A framework for simple tokenizers. Takes care of newlines and
2
// white-space, and of getting the text from the source stream into
3
// the token object. A state is a function of two arguments -- a
4
// string stream and a setState function. The second can be used to
5
// change the tokenizer's state, and can be ignored for stateless
6
// tokenizers. This function should advance the stream over a token
7
// and return a string or object containing information about the next
8
// token, or null to pass and have the (new) state be called to finish
9
// the token. When a string is given, it is wrapped in a {style, type}
10
// object. In the resulting object, the characters consumed are stored
11
// under the content property. Any whitespace following them is also
12
// automatically consumed, and added to the value property. (Thus,
13
// content is the actual meaningful part of the token, while value
14
// contains all the text it spans.)
15
16
function tokenizer(source, state) {
17
  // Newlines are always a separate token.
18
  function isWhiteSpace(ch) {
19
    // The messy regexp is because IE's regexp matcher is of the
20
    // opinion that non-breaking spaces are no whitespace.
21
    return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
22
  }
23
24
  var tokenizer = {
25
    state: state,
26
27
    take: function(type) {
28
      if (typeof(type) == "string")
29
        type = {style: type, type: type};
30
31
      type.content = (type.content || "") + source.get();
32
      if (!/\n$/.test(type.content))
33
        source.nextWhile(isWhiteSpace);
34
      type.value = type.content + source.get();
35
      return type;
36
    },
37
38
    next: function () {
39
      if (!source.more()) throw StopIteration;
40
41
      var type;
42
      if (source.equals("\n")) {
43
        source.next();
44
        return this.take("whitespace");
45
      }
46
47
      if (source.applies(isWhiteSpace))
48
        type = "whitespace";
49
      else
50
        while (!type)
51
          type = this.state(source, function(s) {tokenizer.state = s;});
52
53
      return this.take(type);
54
    }
55
  };
56
  return tokenizer;
57
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/tokenizejavascript.js (+176 lines)
Line 0 Link Here
1
/* Tokenizer for JavaScript code */
2
3
var tokenizeJavaScript = (function() {
4
  // Advance the stream until the given character (not preceded by a
5
  // backslash) is encountered, or the end of the line is reached.
6
  function nextUntilUnescaped(source, end) {
7
    var escaped = false;
8
    var next;
9
    while (!source.endOfLine()) {
10
      var next = source.next();
11
      if (next == end && !escaped)
12
        return false;
13
      escaped = !escaped && next == "\\";
14
    }
15
    return escaped;
16
  }
17
18
  // A map of JavaScript's keywords. The a/b/c keyword distinction is
19
  // very rough, but it gives the parser enough information to parse
20
  // correct code correctly (we don't care that much how we parse
21
  // incorrect code). The style information included in these objects
22
  // is used by the highlighter to pick the correct CSS style for a
23
  // token.
24
  var keywords = function(){
25
    function result(type, style){
26
      return {type: type, style: style};
27
    }
28
    // keywords that take a parenthised expression, and then a
29
    // statement (if)
30
    var keywordA = result("keyword a", "js-keyword");
31
    // keywords that take just a statement (else)
32
    var keywordB = result("keyword b", "js-keyword");
33
    // keywords that optionally take an expression, and form a
34
    // statement (return)
35
    var keywordC = result("keyword c", "js-keyword");
36
    var operator = result("operator", "js-keyword");
37
    var atom = result("atom", "js-atom");
38
    return {
39
      "if": keywordA, "switch": keywordA, "while": keywordA, "with": keywordA,
40
      "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB,
41
      "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC,
42
      "in": operator, "typeof": operator, "instanceof": operator,
43
      "var": result("var", "js-keyword"), "function": result("function", "js-keyword"), "catch": result("catch", "js-keyword"),
44
      "for": result("for", "js-keyword"),
45
      "case": result("case", "js-keyword"), "default": result("default", "js-keyword"),
46
      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
47
    };
48
  }();
49
50
  // Some helper regexp matchers.
51
  var isOperatorChar = matcher(/[+\-*&%\/=<>!?|]/);
52
  var isDigit = matcher(/[0-9]/);
53
  var isHexDigit = matcher(/[0-9A-Fa-f]/);
54
  var isWordChar = matcher(/[\w\$_]/);
55
56
  // Wrapper around jsToken that helps maintain parser state (whether
57
  // we are inside of a multi-line comment and whether the next token
58
  // could be a regular expression).
59
  function jsTokenState(inside, regexp) {
60
    return function(source, setState) {
61
      var newInside = inside;
62
      var type = jsToken(inside, regexp, source, function(c) {newInside = c;});
63
      var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/);
64
      if (newRegexp != regexp || newInside != inside)
65
        setState(jsTokenState(newInside, newRegexp));
66
      return type;
67
    };
68
  }
69
70
  // The token reader, inteded to be used by the tokenizer from
71
  // tokenize.js (through jsTokenState). Advances the source stream
72
  // over a token, and returns an object containing the type and style
73
  // of that token.
74
  function jsToken(inside, regexp, source, setInside) {
75
    function readHexNumber(){
76
      source.next(); // skip the 'x'
77
      source.nextWhile(isHexDigit);
78
      return {type: "number", style: "js-atom"};
79
    }
80
81
    function readNumber() {
82
      source.nextWhile(isDigit);
83
      if (source.equals(".")){
84
        source.next();
85
        source.nextWhile(isDigit);
86
      }
87
      if (source.equals("e") || source.equals("E")){
88
        source.next();
89
        if (source.equals("-"))
90
          source.next();
91
        source.nextWhile(isDigit);
92
      }
93
      return {type: "number", style: "js-atom"};
94
    }
95
    // Read a word, look it up in keywords. If not found, it is a
96
    // variable, otherwise it is a keyword of the type found.
97
    function readWord() {
98
      source.nextWhile(isWordChar);
99
      var word = source.get();
100
      var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word];
101
      return known ? {type: known.type, style: known.style, content: word} :
102
      {type: "variable", style: "js-variable", content: word};
103
    }
104
    function readRegexp() {
105
      nextUntilUnescaped(source, "/");
106
      source.nextWhile(matcher(/[gi]/));
107
      return {type: "regexp", style: "js-string"};
108
    }
109
    // Mutli-line comments are tricky. We want to return the newlines
110
    // embedded in them as regular newline tokens, and then continue
111
    // returning a comment token for every line of the comment. So
112
    // some state has to be saved (inside) to indicate whether we are
113
    // inside a /* */ sequence.
114
    function readMultilineComment(start){
115
      var newInside = "/*";
116
      var maybeEnd = (start == "*");
117
      while (true) {
118
        if (source.endOfLine())
119
          break;
120
        var next = source.next();
121
        if (next == "/" && maybeEnd){
122
          newInside = null;
123
          break;
124
        }
125
        maybeEnd = (next == "*");
126
      }
127
      setInside(newInside);
128
      return {type: "comment", style: "js-comment"};
129
    }
130
    function readOperator() {
131
      source.nextWhile(isOperatorChar);
132
      return {type: "operator", style: "js-operator"};
133
    }
134
    function readString(quote) {
135
      var endBackSlash = nextUntilUnescaped(source, quote);
136
      setInside(endBackSlash ? quote : null);
137
      return {type: "string", style: "js-string"};
138
    }
139
140
    // Fetch the next token. Dispatches on first character in the
141
    // stream, or first two characters when the first is a slash.
142
    if (inside == "\"" || inside == "'")
143
      return readString(inside);
144
    var ch = source.next();
145
    if (inside == "/*")
146
      return readMultilineComment(ch);
147
    else if (ch == "\"" || ch == "'")
148
      return readString(ch);
149
    // with punctuation, the type of the token is the symbol itself
150
    else if (/[\[\]{}\(\),;\:\.]/.test(ch))
151
      return {type: ch, style: "js-punctuation"};
152
    else if (ch == "0" && (source.equals("x") || source.equals("X")))
153
      return readHexNumber();
154
    else if (isDigit(ch))
155
      return readNumber();
156
    else if (ch == "/"){
157
      if (source.equals("*"))
158
      { source.next(); return readMultilineComment(ch); }
159
      else if (source.equals("/"))
160
      { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};}
161
      else if (regexp)
162
        return readRegexp();
163
      else
164
        return readOperator();
165
    }
166
    else if (isOperatorChar(ch))
167
      return readOperator();
168
    else
169
      return readWord();
170
  }
171
172
  // The external interface to the tokenizer.
173
  return function(source, startState) {
174
    return tokenizer(source, startState || jsTokenState(false, true));
175
  };
176
})();
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/undo.js (+388 lines)
Line 0 Link Here
1
/**
2
 * Storage and control for undo information within a CodeMirror
3
 * editor. 'Why on earth is such a complicated mess required for
4
 * that?', I hear you ask. The goal, in implementing this, was to make
5
 * the complexity of storing and reverting undo information depend
6
 * only on the size of the edited or restored content, not on the size
7
 * of the whole document. This makes it necessary to use a kind of
8
 * 'diff' system, which, when applied to a DOM tree, causes some
9
 * complexity and hackery.
10
 *
11
 * In short, the editor 'touches' BR elements as it parses them, and
12
 * the History stores these. When nothing is touched in commitDelay
13
 * milliseconds, the changes are committed: It goes over all touched
14
 * nodes, throws out the ones that did not change since last commit or
15
 * are no longer in the document, and assembles the rest into zero or
16
 * more 'chains' -- arrays of adjacent lines. Links back to these
17
 * chains are added to the BR nodes, while the chain that previously
18
 * spanned these nodes is added to the undo history. Undoing a change
19
 * means taking such a chain off the undo history, restoring its
20
 * content (text is saved per line) and linking it back into the
21
 * document.
22
 */
23
24
// A history object needs to know about the DOM container holding the
25
// document, the maximum amount of undo levels it should store, the
26
// delay (of no input) after which it commits a set of changes, and,
27
// unfortunately, the 'parent' window -- a window that is not in
28
// designMode, and on which setTimeout works in every browser.
29
function History(container, maxDepth, commitDelay, editor, onChange) {
30
  this.container = container;
31
  this.maxDepth = maxDepth; this.commitDelay = commitDelay;
32
  this.editor = editor; this.parent = editor.parent;
33
  this.onChange = onChange;
34
  // This line object represents the initial, empty editor.
35
  var initial = {text: "", from: null, to: null};
36
  // As the borders between lines are represented by BR elements, the
37
  // start of the first line and the end of the last one are
38
  // represented by null. Since you can not store any properties
39
  // (links to line objects) in null, these properties are used in
40
  // those cases.
41
  this.first = initial; this.last = initial;
42
  // Similarly, a 'historyTouched' property is added to the BR in
43
  // front of lines that have already been touched, and 'firstTouched'
44
  // is used for the first line.
45
  this.firstTouched = false;
46
  // History is the set of committed changes, touched is the set of
47
  // nodes touched since the last commit.
48
  this.history = []; this.redoHistory = []; this.touched = [];
49
}
50
51
History.prototype = {
52
  // Schedule a commit (if no other touches come in for commitDelay
53
  // milliseconds).
54
  scheduleCommit: function() {
55
    this.parent.clearTimeout(this.commitTimeout);
56
    this.commitTimeout = this.parent.setTimeout(method(this, "tryCommit"), this.commitDelay);
57
  },
58
59
  // Mark a node as touched. Null is a valid argument.
60
  touch: function(node) {
61
    this.setTouched(node);
62
    this.scheduleCommit();
63
  },
64
65
  // Undo the last change.
66
  undo: function() {
67
    // Make sure pending changes have been committed.
68
    this.commit();
69
70
    if (this.history.length) {
71
      // Take the top diff from the history, apply it, and store its
72
      // shadow in the redo history.
73
      this.redoHistory.push(this.updateTo(this.history.pop(), "applyChain"));
74
      if (this.onChange) this.onChange();
75
    }
76
  },
77
78
  // Redo the last undone change.
79
  redo: function() {
80
    this.commit();
81
    if (this.redoHistory.length) {
82
      // The inverse of undo, basically.
83
      this.addUndoLevel(this.updateTo(this.redoHistory.pop(), "applyChain"));
84
      if (this.onChange) this.onChange();
85
    }
86
  },
87
88
  // Push a changeset into the document.
89
  push: function(from, to, lines) {
90
    var chain = [];
91
    for (var i = 0; i < lines.length; i++) {
92
      var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR");
93
      chain.push({from: from, to: end, text: lines[i]});
94
      from = end;
95
    }
96
    this.pushChains([chain], from == null && to == null);
97
  },
98
99
  pushChains: function(chains, doNotHighlight) {
100
    this.commit(doNotHighlight);
101
    this.addUndoLevel(this.updateTo(chains, "applyChain"));
102
    this.redoHistory = [];
103
  },
104
105
  // Clear the undo history, make the current document the start
106
  // position.
107
  reset: function() {
108
    this.history = []; this.redoHistory = [];
109
  },
110
111
  textAfter: function(br) {
112
    return this.after(br).text;
113
  },
114
115
  nodeAfter: function(br) {
116
    return this.after(br).to;
117
  },
118
119
  nodeBefore: function(br) {
120
    return this.before(br).from;
121
  },
122
123
  // Commit unless there are pending dirty nodes.
124
  tryCommit: function() {
125
    if (this.editor.highlightDirty()) this.commit();
126
    else this.scheduleCommit();
127
  },
128
129
  // Check whether the touched nodes hold any changes, if so, commit
130
  // them.
131
  commit: function(doNotHighlight) {
132
    this.parent.clearTimeout(this.commitTimeout);
133
    // Make sure there are no pending dirty nodes.
134
    if (!doNotHighlight) this.editor.highlightDirty(true);
135
    // Build set of chains.
136
    var chains = this.touchedChains(), self = this;
137
138
    if (chains.length) {
139
      this.addUndoLevel(this.updateTo(chains, "linkChain"));
140
      this.redoHistory = [];
141
      if (this.onChange) this.onChange();
142
    }
143
  },
144
145
  // [ end of public interface ]
146
147
  // Update the document with a given set of chains, return its
148
  // shadow. updateFunc should be "applyChain" or "linkChain". In the
149
  // second case, the chains are taken to correspond the the current
150
  // document, and only the state of the line data is updated. In the
151
  // first case, the content of the chains is also pushed iinto the
152
  // document.
153
  updateTo: function(chains, updateFunc) {
154
    var shadows = [], dirty = [];
155
    for (var i = 0; i < chains.length; i++) {
156
      shadows.push(this.shadowChain(chains[i]));
157
      dirty.push(this[updateFunc](chains[i]));
158
    }
159
    if (updateFunc == "applyChain")
160
      this.notifyDirty(dirty);
161
    return shadows;
162
  },
163
164
  // Notify the editor that some nodes have changed.
165
  notifyDirty: function(nodes) {
166
    forEach(nodes, method(this.editor, "addDirtyNode"))
167
    this.editor.scheduleHighlight();
168
  },
169
170
  // Link a chain into the DOM nodes (or the first/last links for null
171
  // nodes).
172
  linkChain: function(chain) {
173
    for (var i = 0; i < chain.length; i++) {
174
      var line = chain[i];
175
      if (line.from) line.from.historyAfter = line;
176
      else this.first = line;
177
      if (line.to) line.to.historyBefore = line;
178
      else this.last = line;
179
    }
180
  },
181
182
  // Get the line object after/before a given node.
183
  after: function(node) {
184
    return node ? node.historyAfter : this.first;
185
  },
186
  before: function(node) {
187
    return node ? node.historyBefore : this.last;
188
  },
189
190
  // Mark a node as touched if it has not already been marked.
191
  setTouched: function(node) {
192
    if (node) {
193
      if (!node.historyTouched) {
194
        this.touched.push(node);
195
        node.historyTouched = true;
196
      }
197
    }
198
    else {
199
      this.firstTouched = true;
200
    }
201
  },
202
203
  // Store a new set of undo info, throw away info if there is more of
204
  // it than allowed.
205
  addUndoLevel: function(diffs) {
206
    this.history.push(diffs);
207
    if (this.history.length > this.maxDepth)
208
      this.history.shift();
209
  },
210
211
  // Build chains from a set of touched nodes.
212
  touchedChains: function() {
213
    var self = this;
214
    // Compare two strings, treating nbsps as spaces.
215
    function compareText(a, b) {
216
      return a.replace(/\u00a0/g, " ") == b.replace(/\u00a0/g, " ");
217
    }
218
219
    // The temp system is a crummy hack to speed up determining
220
    // whether a (currently touched) node has a line object associated
221
    // with it. nullTemp is used to store the object for the first
222
    // line, other nodes get it stored in their historyTemp property.
223
    var nullTemp = null;
224
    function temp(node) {return node ? node.historyTemp : nullTemp;}
225
    function setTemp(node, line) {
226
      if (node) node.historyTemp = line;
227
      else nullTemp = line;
228
    }
229
230
    function buildLine(node) {
231
      var text = [];
232
      for (var cur = node ? node.nextSibling : self.container.firstChild;
233
           cur && cur.nodeName != "BR"; cur = cur.nextSibling)
234
        if (cur.currentText) text.push(cur.currentText);
235
      return {from: node, to: cur, text: text.join("")};
236
    }
237
238
    // Filter out unchanged lines and nodes that are no longer in the
239
    // document. Build up line objects for remaining nodes.
240
    var lines = [];
241
    if (self.firstTouched) self.touched.push(null);
242
    forEach(self.touched, function(node) {
243
      if (node && node.parentNode != self.container) return;
244
245
      if (node) node.historyTouched = false;
246
      else self.firstTouched = false;
247
248
      var line = buildLine(node), shadow = self.after(node);
249
      if (!shadow || !compareText(shadow.text, line.text) || shadow.to != line.to) {
250
        lines.push(line);
251
        setTemp(node, line);
252
      }
253
    });
254
255
    // Get the BR element after/before the given node.
256
    function nextBR(node, dir) {
257
      var link = dir + "Sibling", search = node[link];
258
      while (search && search.nodeName != "BR")
259
        search = search[link];
260
      return search;
261
    }
262
263
    // Assemble line objects into chains by scanning the DOM tree
264
    // around them.
265
    var chains = []; self.touched = [];
266
    forEach(lines, function(line) {
267
      // Note that this makes the loop skip line objects that have
268
      // been pulled into chains by lines before them.
269
      if (!temp(line.from)) return;
270
271
      var chain = [], curNode = line.from, safe = true;
272
      // Put any line objects (referred to by temp info) before this
273
      // one on the front of the array.
274
      while (true) {
275
        var curLine = temp(curNode);
276
        if (!curLine) {
277
          if (safe) break;
278
          else curLine = buildLine(curNode);
279
        }
280
        chain.unshift(curLine);
281
        setTemp(curNode, null);
282
        if (!curNode) break;
283
        safe = self.after(curNode);
284
        curNode = nextBR(curNode, "previous");
285
      }
286
      curNode = line.to; safe = self.before(line.from);
287
      // Add lines after this one at end of array.
288
      while (true) {
289
        if (!curNode) break;
290
        var curLine = temp(curNode);
291
        if (!curLine) {
292
          if (safe) break;
293
          else curLine = buildLine(curNode);
294
        }
295
        chain.push(curLine);
296
        setTemp(curNode, null);
297
        safe = self.before(curNode);
298
        curNode = nextBR(curNode, "next");
299
      }
300
      chains.push(chain);
301
    });
302
303
    return chains;
304
  },
305
306
  // Find the 'shadow' of a given chain by following the links in the
307
  // DOM nodes at its start and end.
308
  shadowChain: function(chain) {
309
    var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to;
310
    while (true) {
311
      shadows.push(next);
312
      var nextNode = next.to;
313
      if (!nextNode || nextNode == end)
314
        break;
315
      else
316
        next = nextNode.historyAfter || this.before(end);
317
      // (The this.before(end) is a hack -- FF sometimes removes
318
      // properties from BR nodes, in which case the best we can hope
319
      // for is to not break.)
320
    }
321
    return shadows;
322
  },
323
324
  // Update the DOM tree to contain the lines specified in a given
325
  // chain, link this chain into the DOM nodes.
326
  applyChain: function(chain) {
327
    // Some attempt is made to prevent the cursor from jumping
328
    // randomly when an undo or redo happens. It still behaves a bit
329
    // strange sometimes.
330
    var cursor = select.cursorPos(this.container, false), self = this;
331
332
    // Remove all nodes in the DOM tree between from and to (null for
333
    // start/end of container).
334
    function removeRange(from, to) {
335
      var pos = from ? from.nextSibling : self.container.firstChild;
336
      while (pos != to) {
337
        var temp = pos.nextSibling;
338
        removeElement(pos);
339
        pos = temp;
340
      }
341
    }
342
343
    var start = chain[0].from, end = chain[chain.length - 1].to;
344
    // Clear the space where this change has to be made.
345
    removeRange(start, end);
346
347
    // Build a function that will insert nodes before the end node of
348
    // this chain.
349
    var insert = end ?
350
      function(node) {self.container.insertBefore(node, end);}
351
    : function(node) {self.container.appendChild(node);};
352
353
    // Insert the content specified by the chain into the DOM tree.
354
    for (var i = 0; i < chain.length; i++) {
355
      var line = chain[i];
356
      // The start and end of the space are already correct, but BR
357
      // tags inside it have to be put back.
358
      if (i > 0)
359
        insert(line.from);
360
      // Add the text.
361
      var node = makePartSpan(splitSpaces(line.text), this.container.ownerDocument);
362
      insert(node);
363
      // See if the cursor was on this line. Put it back, adjusting
364
      // for changed line length, if it was.
365
      if (cursor && cursor.node == line.from) {
366
        var cursordiff = 0;
367
        var prev = this.after(line.from);
368
        if (prev && i == chain.length - 1) {
369
          // Only adjust if the cursor is after the unchanged part of
370
          // the line.
371
          for (var match = 0; match < cursor.offset &&
372
               line.text.charAt(match) == prev.text.charAt(match); match++);
373
          if (cursor.offset > match)
374
            cursordiff = line.text.length - prev.text.length;
375
        }
376
        select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)});
377
      }
378
      // Cursor was in removed line, this is last new line.
379
      else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) {
380
        select.setCursorPos(this.container, {node: line.from, offset: line.text.length});
381
      }
382
    }
383
384
    // Anchor the chain in the DOM tree.
385
    this.linkChain(chain);
386
    return start;
387
  }
388
};
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/codemirror/js/util.js (+123 lines)
Line 0 Link Here
1
/* A few useful utility functions. */
2
3
// Capture a method on an object.
4
function method(obj, name) {
5
  return function() {obj[name].apply(obj, arguments);};
6
}
7
8
// The value used to signal the end of a sequence in iterators.
9
var StopIteration = {toString: function() {return "StopIteration"}};
10
11
// Checks whether the argument is an iterator or a regular sequence,
12
// turns it into an iterator.
13
function iter(seq) {
14
  var i = 0;
15
  if (seq.next) return seq;
16
  else return {
17
    next: function() {
18
      if (i >= seq.length) throw StopIteration;
19
      else return seq[i++];
20
    }
21
  };
22
}
23
24
// Apply a function to each element in a sequence.
25
function forEach(iter, f) {
26
  if (iter.next) {
27
    try {while (true) f(iter.next());}
28
    catch (e) {if (e != StopIteration) throw e;}
29
  }
30
  else {
31
    for (var i = 0; i < iter.length; i++)
32
      f(iter[i]);
33
  }
34
}
35
36
// Map a function over a sequence, producing an array of results.
37
function map(iter, f) {
38
  var accum = [];
39
  forEach(iter, function(val) {accum.push(f(val));});
40
  return accum;
41
}
42
43
// Create a predicate function that tests a string againsts a given
44
// regular expression.
45
function matcher(regexp){
46
  return function(value){return regexp.test(value);};
47
}
48
49
// Test whether a DOM node has a certain CSS class. Much faster than
50
// the MochiKit equivalent, for some reason.
51
function hasClass(element, className){
52
  var classes = element.className;
53
  return classes && new RegExp("(^| )" + className + "($| )").test(classes);
54
}
55
56
// Insert a DOM node after another node.
57
function insertAfter(newNode, oldNode) {
58
  var parent = oldNode.parentNode;
59
  parent.insertBefore(newNode, oldNode.nextSibling);
60
  return newNode;
61
}
62
63
function removeElement(node) {
64
  if (node.parentNode)
65
    node.parentNode.removeChild(node);
66
}
67
68
function clearElement(node) {
69
  while (node.firstChild)
70
    node.removeChild(node.firstChild);
71
}
72
73
// Check whether a node is contained in another one.
74
function isAncestor(node, child) {
75
  while (child = child.parentNode) {
76
    if (node == child)
77
      return true;
78
  }
79
  return false;
80
}
81
82
// The non-breaking space character.
83
var nbsp = "\u00a0";
84
var matching = {"{": "}", "[": "]", "(": ")",
85
                "}": "{", "]": "[", ")": "("};
86
87
// Standardize a few unportable event properties.
88
function normalizeEvent(event) {
89
  if (!event.stopPropagation) {
90
    event.stopPropagation = function() {this.cancelBubble = true;};
91
    event.preventDefault = function() {this.returnValue = false;};
92
  }
93
  if (!event.stop) {
94
    event.stop = function() {
95
      this.stopPropagation();
96
      this.preventDefault();
97
    };
98
  }
99
100
  if (event.type == "keypress") {
101
    if (event.charCode === 0 || event.charCode == undefined)
102
      event.code = event.keyCode;
103
    else
104
      event.code = event.charCode;
105
    event.character = String.fromCharCode(event.code);
106
  }
107
  return event;
108
}
109
110
// Portably register event handlers.
111
function addEventHandler(node, type, handler, removeFunc) {
112
  function wrapHandler(event) {
113
    handler(normalizeEvent(event || window.event));
114
  }
115
  if (typeof node.addEventListener == "function") {
116
    node.addEventListener(type, wrapHandler, false);
117
    if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);};
118
  }
119
  else {
120
    node.attachEvent("on" + type, wrapHandler);
121
    if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);};
122
  }
123
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref (+7 lines)
Lines 12-17 Cataloging: Link Here
12
                  yes: "Don't display"
12
                  yes: "Don't display"
13
                  no: Display
13
                  no: Display
14
            - descriptions of fields and subfields in the MARC editor.
14
            - descriptions of fields and subfields in the MARC editor.
15
        -
16
            - Use a
17
            - pref: MARCEditor
18
              choices:
19
                  normal: "guided"
20
                  text: "textual"
21
            - editor for MARC records.
15
    Spine Labels:
22
    Spine Labels:
16
        -
23
        -
17
            - When using the quick spine label printer,
24
            - When using the quick spine label printer,
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio-text.tt (-1 / +155 lines)
Line 0 Link Here
0
- 
1
[% INCLUDE 'doc-head-open.inc' %]
2
<title>Koha &rsaquo; Cataloging &rsaquo; [% IF ( biblionumber ) %]Editing [% title |html %] (Record Number [% biblionumber %])[% ELSE %]Add MARC Record[% END %]</title>
3
[% INCLUDE 'doc-head-close.inc' %]
4
<script type="text/javascript" src="[% themelang %]/lib/yui/plugins/bubbling-min.js"></script>
5
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/humanmsg.css" />
6
<script src="[% themelang %]/lib/jquery/plugins/humanmsg.js" type="text/javascript"></script>
7
<style type="text/css">
8
	.controls {clear: both}
9
	#scratchpad {float: right; color: #888; display: none; width: 47%}
10
	#record {float: left; width: 47%}
11
	#close-scratchpad {display: none}
12
</style>
13
</head>
14
<body>
15
<div id="yui-cms-loading">
16
      <div id="yui-cms-float">
17
          Loading, please wait...
18
      </div>
19
  </div>
20
<script type="text/javascript" src="[% themelang %]/lib/yui/plugins/loading-min.js"></script>
21
<script type="text/javascript">
22
//<![CDATA[
23
(function() {
24
	// configuring the loading mask
25
	YAHOO.widget.Loading.config({
26
		opacity: 0.8
27
	});
28
})();
29
//]]>
30
</script>
31
[% INCLUDE 'header.inc' %]
32
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/cataloguing/addbooks.pl">Cataloging</a>  &rsaquo; [% IF ( biblionumber ) %]Editing <em>[% title |html %]</em> (Record Number [% biblionumber %])[% ELSE %]Add MARC Record[% END %]</div>
33
34
<div id="doc" class="yui-t7">
35
36
<div id="bd">
37
        <div id="yui-main">
38
        <div class="yui-g">
39
40
41
42
<h1>[% IF ( biblionumber ) %]Editing <em>[% title |html %]</em> (Record Number [% biblionumber %])</h1>[% ELSE %]Add MARC Record</h1>[% END %]
43
44
[% UNLESS ( number ) %]
45
    <!-- show duplicate warning on tab 0 only -->
46
        [% IF ( duplicatebiblionumber ) %]
47
                    <div class="dialog alert">
48
                        <h4>Duplicate Record suspected</h4>
49
                        <p>Is this a duplicate of <a href="/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=[% duplicatebiblionumber %]" onclick="openWindow('../MARCdetail.pl?biblionumber=[% duplicatebiblionumber %]&amp;popup=1', 'Duplicate biblio'; return false;)">[% duplicatetitle %]</a>?</p>
50
                        <form action="/cgi-bin/koha/cataloguing/additem.pl" method="get">
51
                            <input type="hidden" name="biblionumber" value="[% duplicatebiblionumber %]" />
52
                            <input type="submit" class="edit" value="Yes: Edit existing items" />
53
                        </form>
54
                        <form action="/cgi-bin/koha/cataloguing/addbiblio-text.pl" method="get">
55
                            <input type="submit" class="save" onclick="addbiblio.not_duplicate(); return false;" value="No: Save as New Record" />
56
                        </form>
57
                    </div>
58
        [% END %]
59
    [% END %]
60
61
[% IF ( done ) %]
62
    <script type="text/javascript">
63
        opener.document.forms['f'].biblionumber.value=[% biblionumber %];
64
        opener.document.forms['f'].title.value='[% title |html %]';
65
        window.close();
66
    </script>
67
[% ELSE %]
68
    <form method="post" name="f" id="f" action="/cgi-bin/koha/cataloguing/addbiblio-text.pl" onsubmit="addbiblio.submit(); return false">
69
	<input type="hidden" value="0" id="confirm_not_duplicate" name="confirm_not_duplicate" />
70
[% END %]
71
72
<div id="toolbar">
73
74
<script type="text/javascript">
75
	//<![CDATA[
76
77
	// prepare DOM for YUI Toolbar
78
79
	 $(document).ready(function() {
80
		$("#z3950searchc").empty();
81
	    yuiToolbar();
82
	 });
83
84
	// YUI Toolbar Functions
85
86
	function yuiToolbar() {
87
	    new YAHOO.widget.Button("addbiblio");
88
		new YAHOO.widget.Button({
89
                                            id: "z3950search",
90
                                            type: "button",
91
                                            label: _("z39.50 Search"),
92
                                            container: "z3950searchc",
93
											onclick: {fn:function(){addbiblio.z3950_search()}}
94
                                        });
95
	}
96
97
	//]]>
98
	</script>
99
100
		<ul class="toolbar">
101
			<li><input id="addbiblio" type="submit" value="Save" /></li>
102
			<li id="z3950searchc"><input type="button" id="z3950search" value="z39.50 Search" onclick="PopupZ3950(); return false;" /></li>
103
			<li id="changeframework"><label for="Frameworks">Change framework: </label>
104
			<select name="Frameworks" id="Frameworks" onchange="Changefwk(this);">
105
			                <option value="">Default</option>
106
							[% FOREACH frameworkcodeloo IN frameworkcodeloop %]
107
                                <option value="[% frameworkcodeloo.value %]" [% frameworkcodeloo.selected %]>
108
					             [% frameworkcodeloo.frameworktext %]
109
                                 </option>
110
					        [% END %]
111
			</select>
112
<input type="hidden" name="op" value="addbiblio" /></li>
113
		</ul>
114
</div>
115
116
[% IF ( popup ) %]
117
        <input type="hidden" name="mode" value="popup" />
118
[% END %]
119
        <input type="hidden" name="frameworkcode" value="[% frameworkcode %]" />
120
        <input type="hidden" name="biblionumber" value="[% biblionumber %]" />
121
        <input type="hidden" name="breedingid" value="[% breedingid %]" />
122
		<p>
123
		<label for="itemtypes">Insert Item Type Code for: </label>
124
		<select id="itemtypes">
125
			[% FOREACH itemtype IN itemtypes %]
126
			<option value="[% itemtype.value %]">[% itemtype.description %]</option>
127
			[% END %]
128
		</select>
129
		<button id="insert-itemtype">Insert</button>
130
		</p>
131
132
		<textarea name="record" id="record" rows="20">[% BIG_LOOP %]</textarea>
133
		[% FOREACH HIDDEN_LOO IN HIDDEN_LOOP %]
134
				<input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_indicator1_[% HIDDEN_LOO.index %][% HIDDEN_LOO.random %]" value="" />
135
				<input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_indicator2_[% HIDDEN_LOO.index %][% HIDDEN_LOO.random %]" value="" />
136
				<input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_code_[% HIDDEN_LOO.subfield %]_[% HIDDEN_LOO.index %]_[% HIDDEN_LOO.index_subfield %]" value="[% HIDDEN_LOO.subfield %]" />
137
				<input type="hidden" name="tag_[% HIDDEN_LOO.tag %]_subfield_[% HIDDEN_LOO.subfield %]_[% HIDDEN_LOO.index %]_[% HIDDEN_LOO.index_subfield %]" value="[% HIDDEN_LOO.subfield_value %]" />
138
		[% END %]
139
140
</form>
141
142
</div>
143
</div>
144
145
<script type="text/javascript" src="[% themelang %]/lib/codemirror/js/codemirror.js"></script>
146
<script type="text/javascript" src="[% themelang %]/js/marc.js"></script>
147
<script type="text/javascript" src="[% themelang %]/js/pages/addbiblio-text.js"></script>
148
<script type="text/javascript">
149
	<!--
150
	addbiblio.biblionumber = [% biblionumber or 0 %];
151
	// -->
152
</script>
153
<script type="text/javascript" src="/cgi-bin/koha/cataloguing/framework-jsonp.pl?prepend=addbiblio.mandatory%3D&amp;info=mandatory"></script>
154
155
[% INCLUDE 'intranet-bottom.inc' %]

Return to bug 6707