diff options
Diffstat (limited to 'lib/gcstar/GCBackend/GCBackendXmlParser.pm')
-rw-r--r-- | lib/gcstar/GCBackend/GCBackendXmlParser.pm | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/lib/gcstar/GCBackend/GCBackendXmlParser.pm b/lib/gcstar/GCBackend/GCBackendXmlParser.pm new file mode 100644 index 0000000..091823b --- /dev/null +++ b/lib/gcstar/GCBackend/GCBackendXmlParser.pm @@ -0,0 +1,491 @@ +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 (<COL>) + { + 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 = '<collection'.GCBackend::GCBeXmlBase::hashToXMLString(%attrs).">\n"; + } + elsif ($tag eq 'collectionInlinePreferences') + { + $prefCap = 1; + $anyCap = 1; + $inlinePreferences = '<preferences'.GCBackend::GCBeXmlBase::hashToXMLString(%attrs).">\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 .= '</collection>'; + 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 .= "</$tag>\n"; + } + return; + } + else + { + if ($_[1] eq 'collectionInlinePreferences') + { + $anyCap = $modCap; + $prefCap = 0; + $inlinePreferences .= '</preferences>'; + if ($inlineModel) + { + $globalModelLoader->setCurrentModelFromInline({inlineModel => $inlineModel, + inlinePreferences => $inlinePreferences}); + $inlineModel = ''; + } + } + else + { + $inlinePreferences .= '</'.$_[1].">\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; |