comparison variant_effect_predictor/Bio/SeqIO/game.pm @ 0:1f6dce3d34e0

Uploaded
author mahtabm
date Thu, 11 Apr 2013 02:01:53 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:1f6dce3d34e0
1 # $Id: game.pm,v 1.26.2.1 2003/06/28 22:23:15 jason Exp $
2 #
3 # BioPerl module for Bio::SeqIO::game
4 #
5 # Cared for by Brad Marshall <bradmars@yahoo.com>
6 #
7 # Copyright Ewan Birney & Lincoln Stein & Brad Marshall
8 #
9 # You may distribute this module under the same terms as perl itself
10 # _history
11 # June 25, 2000 written by Brad Marshall
12 #
13 # POD documentation - main docs before the code
14
15 =head1 NAME
16
17 Bio::SeqIO::game - Parses GAME XML 0.1 and higher into and out of Bio::Seq objects.
18
19 =head1 SYNOPSIS
20
21 To use this module you need XML::Parser, XML::Parser::PerlSAX
22 and XML::Writer.
23
24 Do not use this module directly. Use it via the Bio::SeqIO class.
25
26 =head1 DESCRIPTION
27
28 This object can transform Bio::Seq objects to and from bioxml seq,
29 computation, feature and annotation dtds,versions 0.1 and higher.
30 These can be found at http://www.bioxml.org/dtds/current. It does
31 this using the idHandler, seqHandler and featureHandler modules you
32 should have gotten with this one.
33
34 The idea is that any bioxml features can be turned into bioperl
35 annotations. When Annotations and computations are parsed in, they
36 gain additional info in the bioperl SeqFeature tag attribute. These
37 can be used to reconstitute a computation or annotation by the bioxml
38 with the bx-handler module when write_seq is called.
39
40 If you use this to write SeqFeatures that were not generated from
41 computations or annotations, it will output a list of bioxml features.
42 Some data may be lost in this step, since bioxml features just have a
43 span, type and description - nothing about the anlysis performed.
44
45 =head1 FEEDBACK
46
47 =head2 Mailing Lists
48
49 User feedback is an integral part of the evolution of this
50 and other Bioperl modules. Send your comments and suggestions preferably
51 to one of the Bioperl mailing lists.
52 Your participation is much appreciated.
53
54 bioperl-l@bioperl.org - Technical bioperl discussion
55 bioxml-dev@bioxml.org - Technical discussion - Moderate volume
56 bioxml-announce@bioxml.org - General Announcements - Pretty dead
57 http://www.bioxml.org/MailingLists/ - About the mailing lists
58
59 =head1 AUTHOR - Brad Marshall & Ewan Birney & Lincoln Stein
60
61 Email: bradmars@yahoo.com
62 birney@sanger.ac.uk
63 lstein@cshl.org
64
65
66 =head1 CONTRIBUTORS
67
68 Jason Stajich E<lt>jason@bioperl.orgE<gt>
69
70 =head1 APPENDIX
71
72 The rest of the documentation details each of the object
73 methods. Internal methods are usually preceded with a _
74
75 =cut
76
77 # Let the code begin...
78
79 package Bio::SeqIO::game;
80 use vars qw(@ISA);
81 use strict;
82 # Object preamble - inherits from Bio::Root::Object
83
84 use Bio::SeqIO;
85 use Bio::SeqIO::game::seqHandler;
86 use Bio::SeqIO::game::featureHandler;
87 use Bio::SeqIO::game::idHandler;
88 use XML::Parser::PerlSAX;
89 use Bio::SeqFeature::Generic;
90 use XML::Writer;
91
92 use Bio::Seq;
93
94 @ISA = qw(Bio::SeqIO);
95
96
97 sub _initialize {
98 my($self,@args) = @_;
99 $self->SUPER::_initialize(@args);
100 my $xmlfile = "";
101 $self->{'counter'} = 0;
102 $self->{'id_counter'} = 1;
103 $self->{'leftovers'} = undef;
104 $self->{'header'} = undef;
105 $self->{'chunkable'} = undef;
106 $self->{'xmldoc'} = undef;
107
108 $self->_export_subfeatures(1);
109 $self->_group_subfeatures(1);
110 $self->_subfeature_types('exons', 'promoters','poly_A_sites',
111 'utrs','introns','sub_SeqFeature');
112
113 # filehandle is stored by superclass _initialize
114 }
115
116
117 =head2 _export_subfeatures
118
119 Title : _export_subfeatures
120 Usage : $obj->_export_subfeatures
121 Function: export all subfeatures (also in the geneprediction structure)
122 Returns : value of _export_subfeatures
123 Args : newvalue (optional)
124
125 =cut
126
127 sub _export_subfeatures{
128 my $obj = shift;
129 if( @_ ) {
130 my $value = shift;
131 $obj->{'_export_subfeatures'} = $value;
132 }
133 return $obj->{'_export_subfeatures'};
134
135 }
136
137 =head2 _group_subfeatures
138
139 Title : _group_subfeatures
140 Usage : $obj->_group_subfeatures
141 Function: Groups all subfeatures in separate feature_sets
142 Returns : value of _group_subfeatures
143 Args : newvalue (optional)
144
145 =cut
146
147 sub _group_subfeatures{
148 my $obj = shift;
149 if( @_ ) {
150 my $value = shift;
151 $obj->{'_group_subfeatures'} = $value;
152 }
153 return $obj->{'_group_subfeatures'};
154 }
155
156 =head2 _subfeature_types
157
158 Title : _subfeature_types
159 Usage : $obj->_subfeature_types
160 Function: array of all possible subfeatures, it should be a
161 name of a function which
162 : returns an arrau of sub_seqfeatures when called:
163 @array = $feature->subfeaturetype()
164 Returns : array of _subfeature_types
165 Args : array of subfeature types (optional)
166
167 =cut
168
169 sub _subfeature_types{
170 my $obj = shift;
171 if( @_ ) {
172 my @values = @_;
173 $obj->{'_subfeature_types'} = \@values;
174 }
175 return @{$obj->{'_subfeature_types'}};
176
177 }
178
179 =head2 _add_subfeature_type
180
181 Title : _add_subfeature_type
182 Usage : $obj->_add_subfeature_type
183 Function: add one possible subfeature, it should be a name of a function which
184 : returns an arrau of sub_seqfeatures when called: @array = $feature->subfeaturetyp()
185 Returns : 1
186 Args : one subfeature type (optional)
187
188 =cut
189
190 sub _add_subfeature_type{
191 my $obj = shift;
192 if( @_ ) {
193 my @values = @_;
194 push @{$obj->{'_subfeature_types'}}, @values;
195 }
196 return 1;
197
198 }
199
200
201 =head2 next_seq
202
203 Title : next_seq
204 Usage : $seq = $stream->next_seq()
205 Function: returns the next sequence in the stream
206 Returns : Bio::Seq object
207 Args : NONE
208
209 =cut
210
211 sub next_seq {
212 my $self = shift;
213
214
215 # The header is the top level stuff in the XML file.
216 # IE before the first <bx-seq:seq> tag.
217 # If you don't include this in each 'chunk', the
218 # parser will barf.
219 my $header;
220 unless ($self->{'header'}) {
221 while (my $next_line = $self->_readline) {
222 if($next_line=~/<bx-seq:seq?/) {
223 $header .= $`;
224 $self->{'header'}=$header;
225 $self->{'leftovers'} .= "<bx-seq:seq".$';
226 last;
227 } else {
228 $header .= $next_line;
229 }
230 }
231 if ($self->{'header'}=~m|<bx-game:flavor>.*chunkable.*</bx-game:flavor>|) {
232 $self->{'chunkable'}=1;
233 }
234
235 }
236
237 my $not_top_level;
238 my $xmldoc;
239 my $seq;
240 # If chunkable, we read in the document until the next
241 # TOP LEVEL sequence.
242 if ($self->{'chunkable'}) {
243 $xmldoc = $self->{'header'}.$self->{'leftovers'};
244 while (my $next_line = $self->_readline) {
245 # Maintain depth of computations and annotations.
246 # We only want TOP LEVEL seqs if chunkable.
247 while ($next_line=~ m|<bx-computation:computation|g) {
248 $not_top_level++;
249 }
250 while ($next_line=~ m|<bx-annotation:annotation|g) {
251 $not_top_level++;
252 }
253 while ($next_line=~ m|</bx-computation:computation|g) {
254 $not_top_level--;
255 }
256 while ($next_line=~ m|</bx-annotation:annotation|g) {
257 $not_top_level--;
258 }
259 if($next_line=~/<bx-seq:seq?/) {
260 if (!$not_top_level) {
261 $xmldoc .= $`;
262 $self->{'leftovers'} .= "<bx-seq:seq".$';
263 last;
264 }
265 } else {
266 $xmldoc .= $next_line;
267 }
268 }
269 # Make sure the 'doc chunk' has a closing tag
270 # to make the parser happy.
271 if (!$xmldoc=~m|</bx-game:game>|){
272 $xmldoc .= "</bx-game:game>";
273 }
274 # Grab the TOP LEVEL seq..
275 if ($xmldoc =~ m|</bx-seq:seq|) {
276 my $handler = Bio::SeqIO::game::idHandler->new();
277 my $options = {Handler=>$handler};
278 my $parser = XML::Parser::PerlSAX->new($options);
279 $self->{'seqs'} = $parser->parse(Source => { String => $xmldoc });
280 } else { # No sequences.
281 return 0;
282 }
283 # Get the seq out of the array.
284 $seq = @{$self->{'seqs'}}[0];
285 # If not chunkable,
286 # only read document into memory once!
287 } elsif (!$self->{'xmldoc'}) {
288 $self->{'xmldoc'}=$self->{'header'}.$self->{'leftovers'};
289 while (my $next_line = $self->_readline) {
290 $self->{'xmldoc'} .= $next_line;
291 }
292 $xmldoc=$self->{'xmldoc'};
293 # Get the seq id index.
294 if ($xmldoc =~ m|</bx-seq:seq|) {
295 my $handler = Bio::SeqIO::game::idHandler->new();
296 my $options = {Handler=>$handler};
297 my $parser = XML::Parser::PerlSAX->new($options);
298 $self->{'seqs'} = $parser->parse(Source => { String => $xmldoc });
299 $seq = shift @{$self->{'seqs'}};
300 } else { # No sequences.
301 return 0;
302 }
303 my $seq = @{$self->{'seqs'}}[0];
304 # if we already have the doc in memory,
305 # just get the doc.
306 } elsif ($self->{'xmldoc'}) {
307 $xmldoc=$self->{'xmldoc'};
308 $seq = shift @{$self->{'seqs'}};
309 }
310 # If there's more sequences:
311 if ($seq) {
312 # Get the next seq.
313 my $handler = Bio::SeqIO::game::seqHandler->new(-seq => $seq);
314 my $options = {Handler=>$handler};
315 my $parser = XML::Parser::PerlSAX->new($options);
316 my $pseq = $parser->parse(Source => { String => $xmldoc });
317 # get the features.
318 my $fhandler = Bio::SeqIO::game::featureHandler->new($pseq->id(),
319 $pseq->length(),
320 $pseq->alphabet());
321 $options = {Handler=>$fhandler};
322
323 $parser = XML::Parser::PerlSAX->new($options);
324 my $features = $parser->parse(Source => { String => $xmldoc });
325 my $seq = Bio::Seq->new();
326 # Build the Bioperl Seq and return it.
327 foreach my $feature (@{$features}) {
328 $seq->add_SeqFeature($feature);
329 }
330 $seq->primary_seq($pseq);
331 return $seq;
332 } else {
333 return 0;
334 }
335 }
336
337 =head2 next_primary_seq
338
339 Title : next_primary_seq
340 Usage : $seq = $stream->next_primary_seq()
341 Function: returns the next primary sequence (ie no seq_features) in the stream
342 Returns : Bio::PrimarySeq object
343 Args : NONE
344
345 =cut
346
347 sub next_primary_seq {
348 my $self=shift;
349
350 # The header is the top level stuff in the XML file.
351 # IE before the first <bx-seq:seq> tag.
352 # If you don't include this in each 'chunk', the
353 # parser will barf.
354 my $header;
355 unless ($self->{'header'}) {
356 while (my $next_line = $self->_readline) {
357 if($next_line=~/<bx-seq:seq?/) {
358 $header .= $`;
359 $self->{'header'}=$header;
360 $self->{'leftovers'} .= "<bx-seq:seq".$';
361 last;
362 } else {
363 $header .= $next_line;
364 }
365 }
366 if ($self->{'header'}=~m|<bx-game:flavor>.*chunkable.*</bx-game:flavor>|) {
367 $self->{'chunkable'}=1;
368 }
369
370 }
371
372 my $not_top_level = 0;
373 my $xmldoc;
374 my $seq;
375 # If chunkable, we read in the document until the next
376 # TOP LEVEL sequence.
377 if ($self->{'chunkable'}) {
378 $xmldoc = $self->{'header'}.$self->{'leftovers'};
379 while (my $next_line = $self->_readline) {
380 # Maintain depth of computations and annotations.
381 # We only want TOP LEVEL seqs if chunkable.
382 while ($next_line=~ m|<bx-computation:computation|g) {
383 $not_top_level++;
384 }
385 while ($next_line=~ m|<bx-annotation:annotationn|g) {
386 $not_top_level++;
387 }
388 while ($next_line=~ m|</bx-computation:computation|g) {
389 $not_top_level--;
390 }
391 while ($next_line=~ m|</bx-annotation:annotationn|g) {
392 $not_top_level--;
393 }
394 if($next_line=~/<bx-seq:seq?/) {
395 if (!$not_top_level) {
396 $xmldoc .= $`;
397 $self->{'leftovers'} .= "<bx-seq:seq".$';
398 last;
399 }
400 } else {
401 $xmldoc .= $next_line;
402 }
403 }
404 # Make sure the 'doc chunk' has a closing tag
405 # to make the parser happy.
406 if (!$xmldoc=~m|</bx-game:game>|){
407 $xmldoc .= "</bx-game:game>";
408 }
409 # Grab the TOP LEVEL seq..
410 if ($xmldoc =~ m|</bx-seq:seq|) {
411 my $handler = Bio::SeqIO::game::idHandler->new();
412 my $options = {Handler=>$handler};
413 my $parser = XML::Parser::PerlSAX->new($options);
414 $self->{'seqs'} = $parser->parse(Source => { String => $xmldoc });
415 } else { # No sequences.
416 return 0;
417 }
418 $seq = @{$self->{'seqs'}}[0];
419 # If not chunkable,
420 # only read document into memory once!
421 } elsif (!$self->{'xmldoc'}) {
422 $self->{'xmldoc'}=$self->{'header'}.$self->{'leftovers'};
423 while (my $next_line = $self->_readline) {
424 $self->{'xmldoc'} .= $next_line;
425 }
426 $xmldoc=$self->{'xmldoc'};
427 # Get the seq id index.
428 if ($xmldoc =~ m|</bx-seq:seq|) {
429 my $handler = Bio::SeqIO::game::idHandler->new();
430 my $options = {Handler=>$handler};
431 my $parser = XML::Parser::PerlSAX->new($options);
432 $self->{'seqs'} = $parser->parse(Source => { String => $xmldoc });
433 $seq = shift @{$self->{'seqs'}};
434 } else { # No sequences.
435 return 0;
436 }
437 my $seq = @{$self->{'seqs'}}[0];
438 # if we already have the doc in memory,
439 # just get the doc.
440 } elsif ($self->{'xmldoc'}) {
441 $xmldoc=$self->{'xmldoc'};
442 $seq = shift @{$self->{'seqs'}};
443 }
444
445 #print $xmldoc;
446
447 if ($seq) {
448 # Get the next seq.
449 my $handler = Bio::SeqIO::game::seqHandler->new(-seq => $seq);
450 my $options = {Handler=>$handler};
451 my $parser = XML::Parser::PerlSAX->new($options);
452 my $pseq = $parser->parse(Source => { String => $xmldoc });
453 return $pseq;
454 } else {
455 return 0;
456 }
457 }
458
459
460 =head2 write_seq
461
462 Title : write_seq
463 Usage : Not Yet Implemented! $stream->write_seq(@seq)
464 Function: writes the $seq object into the stream
465 Returns : 1 for success and 0 for error
466 Args : Bio::Seq object
467
468
469 =cut
470
471 sub write_seq {
472 my ($self,@seqs) = @_;
473
474 my $bxfeat = "http://www.bioxml.org/dtds/current/feature.dtd";
475 my $bxann = "http://www.bioxml.org/dtds/current/annotation.dtd";
476 my $bxcomp = "http://www.bioxml.org/dtds/current/computation.dtd";
477 my $bxgame = "http://www.bioxml.org/dtds/current/game.dtd";
478 my $bxlink = "http://www.bioxml.org/dtds/current/link.dtd";
479 my $bxseq = "http://www.bioxml.org/dtds/current/seq.dtd";
480
481 my $writer = new XML::Writer(OUTPUT => $self->_fh || \*STDOUT,
482 NAMESPACES => 1,
483 DATA_MODE => 1,
484 DATA_INDENT => 4,
485 PREFIX_MAP => {
486 '' => '', # to keep undef warnings away in XML::Writer, fill in with something as a default prefix later?
487 $bxfeat => 'bx-feature',
488 $bxann => 'bx-annotation',
489 $bxcomp => 'bx-computation',
490 $bxgame => 'bx-game',
491 $bxlink => 'bx-link',
492 $bxseq => 'bx-seq'
493 });
494 $writer->xmlDecl("UTF-8");
495 $writer->doctype("bx-game:game", 'game', $bxgame);
496 $writer ->startTag ([$bxgame, 'game']);
497 $writer->startTag ([$bxgame, 'flavor']);
498 $writer->characters('chunkable');
499 $writer->endTag ([$bxgame, 'flavor']);
500
501 foreach my $seq (@seqs) {
502 $writer->startTag([$bxseq, 'seq'],
503 [$bxseq, 'id'] => $seq->display_id,
504 [$bxseq, 'length'] => $seq->length,
505 [$bxseq, 'type'] => $seq->alphabet);
506 if ($seq->length > 0) {
507 $writer->startTag([$bxseq, 'residues']);
508 $writer->characters($seq->seq);
509 $writer->endTag([$bxseq, 'residues']);
510 }
511 $writer->endTag([$bxseq, 'seq']);
512
513 my @feats = $seq->all_SeqFeatures;
514
515 my $features;
516 foreach my $feature (@feats) {
517 if ($feature->has_tag('annotation_id')) {
518 my @ann_id = $feature->each_tag_value('annotation_id');
519 push (@{$features->{'annotations'}->{$ann_id[0]}}, $feature);
520 } elsif ($feature->has_tag('computation_id')) {
521 my @comp_id = $feature->each_tag_value('computation_id');
522 push (@{$features->{'computations'}->{$comp_id[0]}}, $feature);
523 } else {
524 push (@{$features->{'everybody_else'}}, $feature);
525 }
526 }
527 foreach my $key (keys %{$features->{'annotations'}}) {
528 $writer->startTag([$bxann, 'annotation'],
529 [$bxann, 'id']=>$key
530 );
531 $writer->startTag([$bxann, 'seq_link']);
532 $writer->startTag([$bxlink, 'link']);
533 $writer->emptyTag([$bxlink, 'ref_link'],
534 [$bxlink, 'ref'] => $seq->display_id());
535 $writer->endTag([$bxlink, 'link']);
536 $writer->endTag([$bxann, 'seq_link']);
537 $self->__draw_feature_set($writer, $seq, $bxann, "", @{$features->{'annotations'}->{$key}});
538 $writer->endTag([$bxann, 'annotation']);
539 }
540
541 foreach my $key (keys %{$features->{'computations'}}) {
542 $writer->startTag([$bxcomp, 'computation'],
543 [$bxcomp, 'id']=>$key
544 );
545 $writer->startTag([$bxcomp, 'seq_link']);
546 $writer->startTag([$bxlink, 'link']);
547 $writer->emptyTag([$bxlink, 'ref_link'],
548 [$bxlink, 'ref'] => $seq->display_id());
549 $writer->endTag([$bxlink, 'link']);
550 $writer->endTag([$bxcomp, 'seq_link']);
551 $self->__draw_feature_set($writer, $seq, $bxcomp, "", @{$features->{'computations'}->{$key}});
552 $writer->endTag([$bxcomp, 'computation']);
553 }
554 foreach my $feature (@{$features->{'everybody_else'}}) {
555 $self->__draw_feature($writer, $feature, $seq, "",
556 $self->_export_subfeatures());
557 }
558 }
559 $writer->endTag([$bxgame, 'game']);
560
561 $self->flush if $self->_flush_on_write && defined $self->_fh;
562 return 1;
563 }
564
565
566 #these two subroutines are very specific!
567
568 sub __draw_feature_set {
569 my ($self, $writer, $seq, $namespace, $parent, @features) = @_;
570 my ($feature_set_id);
571
572 my $bxfeat = "http://www.bioxml.org/dtds/current/feature.dtd";
573
574 if ($self->_export_subfeatures() && $self->_group_subfeatures()) {
575 $feature_set_id = $self->{'id_counter'}; $self->{'id_counter'}++;
576 $writer->startTag([$namespace, 'feature_set'],
577 [$namespace, 'id'] => $feature_set_id);
578 foreach my $feature (@features) {
579 $self->__draw_feature($writer, $feature, $seq, $parent , 0);
580 }
581 $writer->endTag([$namespace, 'feature_set']);
582 foreach my $feature (@features) {
583 foreach my $subset ($self->_subfeature_types()) {
584 if (my @subfeatures = eval ( '$feature->' . $subset . '()' )) {
585 my @id = $feature->each_tag_value('id');
586 $self->__draw_feature_set($writer, $seq, $namespace, $id[0], @subfeatures);
587 }
588 }
589 }
590
591 } else {
592 $feature_set_id = $self->{'id_counter'}; $self->{'id_counter'}++;
593 $writer->startTag([$namespace, 'feature_set'],
594 [$namespace, 'id'] => $feature_set_id);
595 foreach my $feature (@features) {
596 $self->__draw_feature($writer, $feature, $seq, "" , $self->_export_subfeatures());
597 }
598 $writer->endTag([$namespace, 'feature_set']);
599 }
600 }
601
602
603 sub __draw_feature {
604 my ($self, $writer, $feature, $seq, $parent, $recursive) = @_;
605 my ($subfeature, $subset, @subfeatures, $score, $score_val, $score_no);
606 my $bxfeat = "http://www.bioxml.org/dtds/current/feature.dtd";
607
608 if (!$feature->has_tag('id')) {
609 $feature->add_tag_value('id', $self->{'id_counter'});
610 $self->{'id_counter'}++;
611 }
612
613 my @id = $feature->each_tag_value('id');
614 if ($parent) {
615 $writer->startTag([$bxfeat, 'feature'],
616 [$bxfeat, 'id'] => $id[0]
617 );
618 } else {
619 $writer->startTag([$bxfeat, 'feature'],
620 [$bxfeat, 'id'] => $id[0],
621 [$bxfeat, 'parent'] => $parent
622 );
623 }
624 $writer->startTag([$bxfeat, 'type']);
625 $writer->characters($feature->primary_tag());
626 $writer->endTag([$bxfeat, 'type']);
627 foreach $score ($feature->all_tags()) {
628 next if ($score eq 'id');
629 $writer->startTag([$bxfeat, 'score'],
630 [$bxfeat, 'type'] => $score
631 );
632 $score_no = 0;
633 foreach $score_val ($feature->each_tag_value($score)) {
634 next unless defined $score_val;
635 $writer->characters(' ') if ($score_no > 0);
636 $writer->characters($score_val);
637 $score_no++;
638 }
639 $writer->endTag([$bxfeat, 'score']);
640 }
641
642 $writer->startTag([$bxfeat, 'seq_relationship'],
643 [$bxfeat, 'seq'] => $seq->display_id,
644 [$bxfeat, 'type'] => 'query'
645 );
646
647 $writer->startTag([$bxfeat, 'span']);
648 $writer->startTag([$bxfeat, 'start']);
649 $writer->characters($feature->start());
650 $writer->endTag([$bxfeat, 'start']);
651 $writer->startTag([$bxfeat, 'end']);
652 $writer->characters($feature->end());
653 $writer->endTag([$bxfeat, 'end']);
654 $writer->endTag([$bxfeat, 'span']);
655 $writer->endTag([$bxfeat, 'seq_relationship']);
656 $writer->endTag([$bxfeat, 'feature']);
657
658 #proces subseqfeature's, exons, introns, promotors, whatever...
659 if ($recursive) {
660 foreach $subset ($self->_subfeature_types()) {
661 #determine if it exists
662 if (@subfeatures = eval ( '$feature->' . $subset . '()' )) {
663 foreach $subfeature (@subfeatures) {
664 $self->__draw_feature ($writer, $subfeature, $seq, $id[0], 1);
665 }
666 }
667 }
668 }
669 }
670
671 1;
672