package GCBackend::GCBackendXmlParser; ################################################### # # Copyright 2005-2010 Christian Jodar # # This file is part of GCstar. # # GCstar is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # GCstar is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with GCstar; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA # ################################################### use strict; use utf8; use filetest 'access'; use GCBackend::GCBackendXmlCommon; { package GCBackend::GCBeXmlParser; use File::Temp qw/ tempfile /; use File::Copy; use base 'GCBackend::GCBeXmlBase'; my $globalInstance; my $globalSplash; my $globalModelLoader; #my @data; #my %information; #my %histories; my $maxId; my $savedMaxId; my $historyInline; sub load { my ($self, $splash) = @_; if (! -r $self->{file}) { my @error = ('OpenError', ''); return {error => \@error}; } $self->{data} = []; $self->{information} = {}; $self->{histories} = (); $maxId = 0; $savedMaxId = 0; $globalInstance = $self; $globalSplash = $splash; $globalModelLoader = $self->{modelLoader}; my $parser = XML::Parser->new(Handlers => { Init => \&StartDocument, Final => \&EndDocument, Start => \&StartTag, End => \&EndTag, Char => \&Text, }); # We have to preload the model into cache because XML::Parser is not # re-entrant. Then when we begin parsing, we cannot parse the model $self->prepareModel($self->{file}); my $error = undef; while (1) { eval { $parser->parsefile($self->{file}); }; if ($@) { my $errorDesc = $@; # Here we will fix the collection if an invalid character was found by trying to remove it. # There should be room for optimisation here if ($errorDesc =~ /not\s*well-formed\s*\(invalid\s*token\)\s*.*?byte\s*(\d+)/) { my $charPosition = $1; # We would have failed before if it cannot be opened, so we don't check that. open COL, $self->{file}; seek COL, $charPosition, 0; my $badChar; read COL, $badChar, 1; seek COL, 0, 0; (my ($newCol, $tmpFile)) = tempfile(); while () { s/$badChar//g; print $newCol $_; } close $newCol; close COL; move($tmpFile, $self->{file}); } else { $errorDesc =~ s/^\n*//; my @errorArray = ('OpenFormatError', $errorDesc); $error = \@errorArray; last; } } else { last; } } # TODO : Compare performances with and without the compact below because the duplicates are checked # also when adding to the graphical components # Compact histories. We didn't filtered previously for performances issues #GCUtils::compactHistories(\%histories); $self->{information}->{maxId} = $maxId if ! exists $self->{information}->{maxId}; # gotHistory: # 0: Nothing done # 1: Returning history # 2: Already initialized return { error => $error, data => $self->{data}, information => $self->{information}, histories => \$self->{histories}, gotHistory => (1 + ($historyInline ? 0 : 1)), # We always have an initalized history with this BE. }; } # Parser routines # Some globals to speed up things my $inCol; my $inLine; my $currentTag; my $currentCol; my $currentCount; my $currentIsList; my $isItem; my $isInfo; my $newItem; my $modCap; my $prefCap; my $anyCap; my $isInline; my $inlineModel; my $inlinePreferences; my $inHistories; my $historyField; # history type : # 1 : Single list # 2 : Multiple list my $historyType; my $historyCap; sub StartDocument { $isItem = 0; $inLine = 0; $inCol = 0; $currentCol = ''; $currentCount = 0; $modCap = 0; $prefCap = 0; $anyCap = 0; $inlineModel = ''; $inlinePreferences = ''; # SAVED HISTORIES DEACTIVATED # $inHistories = 0; # $historyField = ''; # $historyCap = 0; $historyInline = 0; } sub EndDocument { if (($inlineModel) && ($isInline)) { $globalModelLoader->setCurrentModelFromInline({inlineModel => $inlineModel, inlinePreferences => $inlinePreferences}); } } sub StartTag { #my ($expat, $tag, %attrs) = @_; if ($isItem) { if ($inLine) { #Only a col could start in a line $inCol = 1; } elsif ($_[1] eq 'line') { $inLine = 1; $currentIsList = 1; $newItem->{$currentTag} = [] if (ref($newItem->{$currentTag}) ne 'ARRAY'); push @{$newItem->{$currentTag}}, []; } else { $currentIsList = 0; $currentTag = $_[1]; } } elsif ($isInfo) { $currentTag = $_[1]; $savedMaxId = 1 if $currentTag eq 'maxId'; } else { my ($expat, $tag, %attrs) = @_; if ($modCap) { $tag =~ s/^user(.)/\L$1\E/;; $inlineModel .= "<$tag".GCBackend::GCBeXmlBase::hashToXMLString(%attrs).'>'; } elsif ($prefCap) { $inlinePreferences .= "<$tag".GCBackend::GCBeXmlBase::hashToXMLString(%attrs).'>'; } elsif ($tag eq 'item') { $newItem = \%attrs; $isItem = 1; } elsif ($tag eq 'information') { $isInfo = 1; } elsif (($tag eq 'collectionInlineDescription') || ($tag eq 'userCollection')) { $modCap = 1; $anyCap = 1; $inlineModel = '\n"; } elsif ($tag eq 'collectionInlinePreferences') { $prefCap = 1; $anyCap = 1; $inlinePreferences = '\n"; } elsif ($tag eq 'collection') { $globalSplash->setItemsTotal($attrs{items}) if $globalSplash; if ($attrs{type} eq 'inline') { $isInline = 0; } else { if (! $globalModelLoader->setCurrentModel($attrs{type})) { die $globalModelLoader->{lang}->{ErrorModelNotFound}.$attrs{type} ."\n\n" .$globalModelLoader->getUserModelsDirError."\n"; } } } # SAVED HISTORIES DEACTIVATED # elsif ($tag eq 'histories') # { # $inHistories = 1; # $historyInline = 1; # } # elsif ($inHistories) # { # if ($tag eq 'history') # { # $historyField = $attrs{name}; # # Default is single # $historyType = 1; # } # elsif ($tag eq 'values') # { # push @{$globalInstance->{histories}->{$historyField}}, []; # $historyType = 2; # } # elsif ($tag eq 'value') # { # if ($historyType == 1) # { # push @{$globalInstance->{histories}->{$historyField}}, ''; # } # else # { # push @{$globalInstance->{histories}->{$historyField}->[-1]}, ''; # } # $historyCap = 1; # } # } } } sub EndTag { if ($anyCap) { if ($modCap) { if (($_[1] eq 'collectionInlineDescription') || ($_[1] eq 'userCollection')) { $anyCap = $prefCap; $modCap = 0; $inlineModel .= ''; if ($inlinePreferences) { $globalModelLoader->setCurrentModelFromInline({inlineModel => $inlineModel, inlinePreferences => $inlinePreferences}); $inlineModel = undef; } elsif($_[1] eq 'userCollection') { $globalModelLoader->addFieldsToDefaultModel($inlineModel); $inlineModel = undef; } } else { (my $tag = $_[1]) =~ s/^user(.)/\L$1\E/; $inlineModel .= "\n"; } return; } else { if ($_[1] eq 'collectionInlinePreferences') { $anyCap = $modCap; $prefCap = 0; $inlinePreferences .= ''; if ($inlineModel) { $globalModelLoader->setCurrentModelFromInline({inlineModel => $inlineModel, inlinePreferences => $inlinePreferences}); $inlineModel = ''; } } else { $inlinePreferences .= '\n"; } return; } } if ($_[1] eq 'item') { push @{$globalInstance->{data}}, $newItem; $currentCount++; # SAVED HISTORIES DEACTIVATED # if (!$historyInline) # { #foreach (@{$globalModelLoader->{model}->{fieldsHistory}}) #{ # push @{$globalInstance->{histories}->{$_}}, $newItem->{$_}; #} if ($globalModelLoader->{panel}) { foreach (@{$globalModelLoader->{model}->{fieldsHistory}}) { $globalModelLoader->{panel}->{$_}->addHistory($newItem->{$_}, 1); } } # } foreach (@{$globalModelLoader->{model}->{fieldsNotNull}}) { $newItem->{$_} = $globalModelLoader->{model}->{fieldsInfo}->{$_}->{init} if ! $newItem->{$_}; } if (!$savedMaxId) { my $id = $newItem->{$globalModelLoader->{model}->{commonFields}->{id}}; $maxId = $id if $id > $maxId; } $globalSplash->setProgressForItemsLoad($currentCount) if $globalSplash; $isItem = 0; } elsif ($_[1] eq 'information') { $isInfo = 0 if !$isItem; } elsif ($inCol) { # We are closing a col as it could not have tags inside push @{$newItem->{$currentTag}->[-1]}, $currentCol; $currentCol = ''; $inCol = 0; } # SAVED HISTORIES DEACTIVATED # elsif ($inHistories) # { # $inHistories = 0 if $_[1] eq 'histories'; # $historyField = '' if $_[1] eq 'history'; # $historyCap = 0 if $_[1] eq 'value'; # # } else { # The only tag that could prevent us from closing a line is col, but it has # already been managed if ($inLine) { $inLine = 0; } else { $currentTag = ''; } } } sub Text { if ($isItem) { if ((! $currentTag) || $inLine || $currentIsList || ((!$newItem->{$currentTag}) && ($_[1] =~ /^\s*$/oms))) { if ($inCol) { return if $_[1] =~ /^\s*$/oms; $currentCol .= $_[1]; } } else { $newItem->{$currentTag} .= $_[1]; } } elsif ($isInfo) { return if $_[1] =~ /^\s*$/oms; $globalInstance->{information}->{$currentTag} .= $_[1]; } else { if ($modCap) { $inlineModel .= $_[1]; } elsif ($prefCap) { $inlinePreferences .= $_[1]; } # elsif ($historyCap) # { # if ($historyType == 1) # { # $globalInstance->{histories}->{$historyField}->[-1] .= $_[1]; # } # else # { # $globalInstance->{histories}->{$historyField}->[-1]->[-1] .= $_[1]; # } # } } } } 1;