# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*- # # This file is MPL/GPL dual-licensed under the following terms: # # The contents of this file are subject to the Mozilla Public License # Version 1.1 (the "License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and # limitations under the License. # # The Original Code is PLIF 1.0. # The Initial Developer of the Original Code is Ian Hickson. # # Alternatively, the contents of this file may be used under the terms # of the GNU General Public License Version 2 or later (the "GPL"), in # which case the provisions of the GPL are applicable instead of those # above. If you wish to allow use of your version of this file only # under the terms of the GPL and not to allow others to use your # version of this file under the MPL, indicate your decision by # deleting the provisions above and replace them with the notice and # other provisions required by the GPL. If you do not delete the # provisions above, a recipient may use your version of this file # under either the MPL or the GPL. package PLIF::Service::Components::CosesEditor; use strict; use vars qw(@ISA); use PLIF::Service; @ISA = qw(PLIF::Service); 1; # XXX Add more fine grained control over rights (this would have the # side-effect of removing the redundancy in each of the cmdXXX calls # below, which would be nice...) sub provides { my $class = shift; my($service) = @_; return ($service eq 'component.cosesEditor' or $service eq 'dispatcher.commands' or $service eq 'dispatcher.output.generic' or $service eq 'dispatcher.output' or $service eq 'setup.install' or $class->SUPER::provides($service)); } # dispatcher.commands sub cmdCosesEditor { # warning: this is also called from other methods below my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { my $variants = $self->getDescribedVariants($app); my $variantsSortColumn = $app->input->getArgument('cosesEditor.variantsSortColumn'); my $strings = $self->getExpectedStrings($app); my $stringsSortColumn = $app->input->getArgument('cosesEditor.stringsSortColumn'); # if (defined($user)) { $user->setting(\$variantsSortColumn, 'cosesEditor.index.variantsSortColumn'); $user->setting(\$stringsSortColumn, 'cosesEditor.index.stringsSortColumn'); # } $app->output->cosesEditorIndex($variants, $variantsSortColumn, $strings, $stringsSortColumn); } # else, user has been notified } # dispatcher.commands sub cmdCosesVariantAdd { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { my $protocol = $app->input->getArgument('cosesEditor.variantProtocol'); my @data = ('', $protocol, 1.0, '', '', '', '', '', ''); my $id = $app->getService('dataSource.strings.customised')->setVariant($app, undef, @data); my $expectedStrings = $self->getExpectedStrings($app, $protocol); $app->output->cosesEditorVariant($id, @data, $expectedStrings, {}); } # else, user has been notified } # dispatcher.commands sub cmdCosesVariantEdit { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { my $id = $app->input->getArgument('cosesEditor.variantID'); my $dataSource = $app->getService('dataSource.strings.customised'); my @data = $dataSource->getVariant($app, $id); my $expectedStrings = $self->getExpectedStrings($app, $data[1]); my $variantStrings = \$dataSource->getVariantStrings($app, $id); $app->output->cosesEditorVariant($id, @data, $expectedStrings, $variantStrings); } # else, user has been notified } # dispatcher.commands sub cmdCosesVariantAddString { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { my($id, $data, $variantStrings) = $self->getVariantEditorArguments($app); my $expectedStrings = $self->getExpectedStrings($app, $data->[1]); $app->output->cosesEditorVariant($id, @$data, $expectedStrings, $variantStrings); } # else, user has been notified } # dispatcher.commands sub cmdCosesVariantCommit { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { my($id, $data, $variantStrings) = $self->getVariantEditorArguments($app); my $dataSource = $app->getService('dataSource.strings.customised'); $dataSource->setVariant($app, $id, @$data); foreach my $string (keys(%$variantStrings)) { $dataSource->setString($app, $id, $string, @{$variantStrings->{$string}}); } $self->cmdCosesEditor($app); } # else, user has been notified } # dispatcher.commands sub cmdCosesStringEdit { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { my $id = $app->input->getArgument('cosesEditor.stringID'); my $strings = $self->getExpectedStrings($app); my $expectedVariants = $self->getDescribedVariants($app, $id); my $stringVariants = \$app->getService('dataSource.strings.customised')->getStringVariants($app, $id); $app->output->cosesEditorString($id, $strings->{$id}, $expectedVariants, $stringVariants); } # else, user has been notified } # dispatcher.commands sub cmdCosesStringCommit { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { my $input = $app->input; my $id = $input->getArgument('cosesEditor.stringID'); my %variants; my $index = 0; while (defined(my $name = $input->getArgument('cosesEditor.stringVariant.$index.name'))) { $variants{$name} = [$input->getArgument('cosesEditor.stringVariant.$index.type'), $input->getArgument('cosesEditor.stringVariant.$index.version'), $input->getArgument('cosesEditor.stringVariant.$index.value')]; $index += 1; } my $dataSource = $app->getService('dataSource.strings.customised'); foreach my $variant (keys(%variants)) { $dataSource->setString($app, $variant, $id, @{$variants{$variant}}); } $self->cmdCosesEditor($app); } # else, user has been notified } # dispatcher.commands sub cmdCosesVariantExport { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { # get data my $id = $app->input->getArgument('cosesEditor.variantID'); my $dataSource = $app->getService('dataSource.strings.customised'); my @data = $dataSource->getVariant($app, $id); my %strings = $dataSource->getVariantStrings($app, $id); # serialise variant my $XML = $app->getService('service.xml'); # note. This namespace is certainly not set in stone. Please make better suggestions. XXX my $result = 'escape(shift(@data)); $result .= "\n $name=\"$value\""; } $result .= ">\n"; foreach my $string (keys(%strings)) { my $name = $XML->escape($string); my $type = $XML->escape($strings{$string}->[0]); my $version = $XML->escape($strings{$string}->[1]); my $value = $XML->escape($strings{$string}->[2]); $result.= " $value\n"; } $result .= ''; $app->output->cosesEditorExport($id, $result); } # else, user has been notified } # dispatcher.commands sub cmdCosesVariantImport { my $self = shift; my($app) = @_; my $user = $app->getService('user.login')->hasRight($app, 'cosesEditor'); if (defined($user)) { # get data my $file = $app->input->getArgument('cosesEditor.importData'); # parse data my $XML = $app->getService('service.xml'); my $data = { 'depth' => 0, 'name' => undef, 'type' => undef, 'version' => undef, 'variant' => [], # same at all scopes (because walkNesting() is not a deep copy) 'strings' => {}, # same at all scopes (because walkNesting() is not a deep copy) }; $XML->walk($self, $XML->parse($file), $data); # add data my $dataSource = $app->getService('dataSource.strings.customised'); my $id = $dataSource->setVariant($app, undef, @{$data->{'variant'}}); foreach my $string (keys(%{$data->{'strings'}})) { $dataSource->setString($app, $id, $string, @{$data->{'strings'}->{$string}}); } # display data my %expectedStrings = $self->getExpectedStrings($app); my %variantStrings = $dataSource->getVariantStrings($app, $id); $app->output->cosesEditorVariant($id, @{$data->{'variant'}}, \%expectedStrings, \%variantStrings); } # else, user has been notified } # service.xml.sink sub walkElement { my $self = shift; my($tagName, $attributes, $tree, $data) = @_; if ($tagName eq '{http://bugzilla.mozilla.org/variant/1}variant') { if ($data->{'depth'} == 0) { foreach my $name (qw(protocol quality type encoding charset language description translator)) { if (exists($attributes->{$name})) { push(@{$data->{'variant'}}, $attributes->{$name}); } else { $self->error(1, "invalid variant document format - missing attribute '$name' on "); } } } else { $self->error(1, 'invalid variant document format - not root element'); } } elsif ($tagName eq '{http://bugzilla.mozilla.org/variant/1}string') { if ($data->{'depth'} == 1) { foreach my $name (qw(name type version)) { if (exists($attributes->{$name})) { $data->{$name} = $attributes->{$name}; } else { $self->error(1, "invalid variant document format - missing attribute '$name' on "); } } } else { $self->error(1, 'invalid variant document format - not child of '); } } else { $self->error(1, "invalid variant document format - unexpected tag <$tagName>"); } $data->{'depth'} += 1; } # service.xml.sink sub walkText { my $self = shift; my($text, $data) = @_; if (defined($data->{'string'})) { $data->{'strings'}->{$data->{'name'}} = [$data->{'type'}, $data->{'version'}, $text]; } elsif ($text !~ /^\w*$/o) { $self->error(1, "invalid variant document format - unexpected text"); } } # service.xml.sink sub walkNesting { my $self = shift; my($data) = @_; my %data = %$data; # copy first level of data hash return \%data; # return reference to copy } # dispatcher.output.generic sub outputCosesEditorIndex { my $self = shift; my($app, $output, $variants, $variantsSortColumn, $strings, $stringsSortColumn) = @_; $output->output('cosesEditor.index', { 'variants' => $variants, 'variantsSortColumn' => $variantsSortColumn, 'strings' => $strings, 'stringsSortcolumn' => $stringsSortColumn, }); } # dispatcher.output.generic sub outputCosesEditorVariant { my $self = shift; my($app, $output, $variant, $protocol, $quality, $type, $encoding, $charset, $language, $description, $translator, $expectedStrings, $variantStrings) = @_; $output->output('cosesEditor.variant', { 'variant' => $variant, 'protocol' => $protocol, 'quality' => $quality, 'type' => $type, 'encoding' => $encoding, 'charset' => $charset, 'language' => $language, 'description' => $description, 'translator' => $translator, 'expectedStrings' => $expectedStrings, 'variantStrings' => $variantStrings, }); } # dispatcher.output.generic sub outputCosesEditorString { my $self = shift; my($app, $output, $string, $description, $expectedVariants, $stringVariants) = @_; $output->output('cosesEditor.string', { 'string' => $string, 'description' => $description, 'expectedVariants' => $expectedVariants, 'stringVariants' => $stringVariants, }); } # dispatcher.output.generic sub outputCosesEditorExport { my $self = shift; my($app, $output, $variant, $result) = @_; $output->output('cosesEditor.export', { 'variant' => $variant, 'output' => $result, }); } # dispatcher.output sub strings { return ( 'cosesEditor.index' => 'The COSES editor index. The variants hash (variant ID => hash with keys name, protocol, quality, type, encoding, charset, language, description, and translator) should be sorted by the variantsSortColumn, and the strings hash (name=>description) should be sorted by the stringsSortColumn (these are typically set by the cosesEditor.variantsSortColumn and cosesEditor.stringsSortColumn arguments). Typical commands that this should lead to: cosesVariantAdd (optional cosesEditor.variantProtocol), cosesVariantEdit (cosesEditor.variantID), cosesStringEdit (cosesEditor.stringID), cosesVariantExport (cosesEditor.variantID), cosesVariantImport (cosesEditor.importData, the contents of an XML file)', 'cosesEditor.variant' => 'The COSES variant editor. The data hash contains: protocol, quality, type, encoding, charset, language, description and translator (hereon "the variant data"), variant, an expectedStrings hash (name=>description), and a variantStrings hash (name=>[type,version,value]). The two hashes are likely to overlap. Typical commands that this should lead to: cosesVariantCommit and cosesVariantAddString (cosesEditor.variantID, cosesEditor.variantX where X is each of the variant data, cosesEditor.variantString.N.name, cosesEditor.variantString.N.type, cosesEditor.variantString.N.version, and cosesEditor.variantString.N.value where N is a number from 0 to as high as required, and cosesEditor.string.new.name, cosesEditor.string.new.type, cosesEditor.string.new.version and cosesEditor.variantString.new.value)', 'cosesEditor.string' => 'The COSES string editor. The name of the string being edited and its description are in string and description. The expectedVariants contains a list of all variants (variant ID => hash with keys name, protocol, quality, type, encoding, charset, language, description, and translator), and stringVariants hosts the currently set strings (variant=>value). The main command that this should lead to is: cosesStringCommit (cosesEditor.stringID, cosesEditor.stringVariant.N.name, cosesEditor.stringVariant.N.type, cosesEditor.stringVariant.N.version and cosesEditor.stringVariant.N.value where N is a number from 0 to as high as required)', 'cosesEditor.export' => 'The COSES variant export feature. variant holds the id of the variant, and output holds the XML representation of the variant.', ); } # setup.install # XXX at least part of this could also be implemented as a user field # factory registerer hook -- does this matter? sub setupInstall { my $self = shift; my($app) = @_; $self->dump(9, 'about to configure COSES editor...'); my $fieldFactory = $app->getService('user.fieldFactory'); $fieldFactory->registerSetting($app, 'cosesEditor.index.stringsSortColumn', 'string'); $fieldFactory->registerSetting($app, 'cosesEditor.index.variantsSortColumn', 'string'); my $userDataSource = $app->getService('dataSource.user'); $userDataSource->addRight($app, 'cosesEditor'); $self->dump(9, 'done configuring COSES editor'); return; } # Internal Routines sub getVariantEditorArguments { my $self = shift; my($app) = @_; my $input = $app->input; my $id = $input->getArgument('cosesEditor.variantID'); my @data = (); foreach my $argument (qw(name protocol quality type encoding charset language description translator)) { push(@data, $input->getArgument('cosesEditor.variant\u$argument')); } my %variantStrings = (); my $index = 0; while (defined(my $name = $input->getArgument('cosesEditor.variantString.$index.name'))) { $variantStrings{$name} = [$input->getArgument('cosesEditor.variantString.$index.type'), $input->getArgument('cosesEditor.variantString.$index.version'), $input->getArgument('cosesEditor.variantString.$index.value')]; $index += 1; } my $newName = $input->getArgument('cosesEditorVariantStringNewName'); if ((defined($newName)) and ($newName ne '')) { $variantStrings{$newName} = [$input->getArgument('cosesEditor.variantString.new.type'), $input->getArgument('cosesEditor.variantString.new.version'), $input->getArgument('cosesEditor.variantString.new.value')]; } return ($id, \@data, \%variantStrings); } sub getExpectedStrings { my $self = shift; my($app, $protocol) = @_; my $strings = $app->getCollectingServiceList('dispatcher.output')->strings(); if (defined($protocol)) { my $defaults = $app->getSelectingService('dataSource.strings.default'); foreach my $string (keys(%$strings)) { $strings->{$string} = { 'description' => $strings->{$string}, 'default' => $defaults->getDefaultString($app, $protocol, $string), }; } } return $strings; } sub getDescribedVariants { my $self = shift; my($app, $string) = @_; my $variants = \$app->getService('dataSource.strings.customised')->getDescribedVariants($app); if (defined($string)) { my $defaults = $app->getSelectingService('dataSource.strings.default'); foreach my $variant (keys(%$variants)) { push(@{$variants->{$variant}}, $defaults->getDefaultString($app, $variants->{$variant}->[1], $string)); } } return $variants; }