epic-ts
authorMichael Wagner <michael@wagnertech.de>
Fri, 24 Sep 2021 19:42:26 +0000 (21:42 +0200)
committerMichael Wagner <michael@wagnertech.de>
Fri, 24 Sep 2021 19:42:26 +0000 (21:42 +0200)
SL/Clipboard/Base.pm [deleted file]
SL/Clipboard/RequirementSpecItem.pm [deleted file]
SL/Clipboard/RequirementSpecPicture.pm [deleted file]
SL/Clipboard/RequirementSpecTextBlock.pm [deleted file]
SL/Controller/CsvImport/Clipboard/Base.pm [new file with mode: 0644]
SL/Controller/CsvImport/Clipboard/RequirementSpecItem.pm [new file with mode: 0644]
SL/Controller/CsvImport/Clipboard/RequirementSpecPicture.pm [new file with mode: 0644]
SL/Controller/CsvImport/Clipboard/RequirementSpecTextBlock.pm [new file with mode: 0644]

diff --git a/SL/Clipboard/Base.pm b/SL/Clipboard/Base.pm
deleted file mode 100644 (file)
index fc0a16a..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-package SL::Clipboard::Base;
-
-use strict;
-
-use parent qw(Rose::Object);
-
-use Rose::Object::MakeMethods::Generic (
-  'scalar --get_set_init' => [ qw(content timestamp) ],
-);
-
-use Rose::DB::Object::Helpers ();
-
-sub init_timestamp { die "'timestamp' property not set"; }
-sub init_content   { die "'content' property not set";   }
-
-sub type {
-  my ($self_or_class) = @_;
-  return (split m/::/, ref($self_or_class) ? ref($self_or_class) : $self_or_class)[-1];
-}
-
-sub reload_object {
-  my ($self, $object) = @_;
-
-  return ref($object)->new(map { $_ => $object->$_ } $object->meta->primary_key)->load;
-}
-
-sub as_tree {
-  my ($self, $object, %params) = @_;
-
-  my $tree = Rose::DB::Object::Helpers::as_tree($object, %params);
-  $self->_fix_tree($tree, $object);
-  return $tree;
-}
-
-sub to_object {
-  my ($self) = @_;
-  my $object = Rose::DB::Object::Helpers::new_from_tree("SL::DB::" . $self->type, $self->content);
-
-  # Reset primary key columns and itime/mtime if the class supports it.
-  foreach ($object->meta->primary_key, 'itime', 'mtime') {
-    $object->$_(undef) if $object->can($_);
-  }
-
-  # Let sub classes fix the objects further.
-  $self->_fix_object($object);
-  return $object;
-}
-
-sub dump {
-  my ($self, $object) = @_;
-  return $self->as_tree($self->reload_object($object), max_depth => 1);
-}
-
-sub describe {
-  die "'describe' method not overwritten by derived class";
-}
-
-sub _fix_object {
-  my ($self, $object) = @_;
-  # To be overwritten by child classes.
-}
-
-sub _fix_tree {
-  my ($self, $tree, $object) = @_;
-
-  # Delete primary key columns and itime/mtime if the class supports it.
-  foreach ($object->meta->primary_key, 'itime', 'mtime') {
-    delete $tree->{$_} if $object->can($_);
-  }
-}
-
-sub _binary_column_names {
-  my ($self, $class) = @_;
-  return map  { $_->name }
-         grep { ref($_) =~ m/Pg::Bytea$/i }
-         @{ $class->meta->columns };
-}
-
-1;
-__END__
-
-=pod
-
-=encoding utf8
-
-=head1 NAME
-
-SL::Clipboard::Base - Base class for clipboard specialization classes
-
-=head1 SYNOPSIS
-
-See the synopsis of L<SL::Clipboard>.
-
-=head1 OVERVIEW
-
-This is a base class providing a lot of utility and
-defaults. Sub-classes must overwrite at least the function
-L</describe> but can overwrite others as well.
-
-Writing a specialized sub-class for a database type involves
-overwriting one or more functions. These are:
-
-=over 4
-
-=item * C<describe>
-
-Must be overwritten. Returns a human-readable description of the
-content. Should only be one line.
-
-=item * C<dump>
-
-Optional. Overwrite if sub-class needs to dump more/less than the
-implementation in this class dumps.
-
-=item * C<_fix_object>
-
-Optional. Overwrite if re-created Rose::DB::Object instances must be
-cleaned further before they're returned to the caller.
-
-=item * C<_fix_tree>
-
-Optional. Overwrite if the tree created during a copy operation of a
-Rose::DB::Object instance must be cleaned further before it's stored.
-
-=back
-
-You don't have to or should not overwrite the other functions:
-
-=over 4
-
-=item * C<as_tree>
-
-=item * C<reload_object>
-
-=item * C<to_object>
-
-=item * C<type>
-
-=back
-
-Don't forget to C<use> the specialized module here in Base!
-
-=head1 FUNCTIONS
-
-=over 4
-
-=item C<as_tree $object, %params>
-
-A convenience function calling L<Rose::DB::Object::Helpers/as_tree>
-with C<$object> and C<%params> as parameters. Returns a hash/array
-reference tree of the function.
-
-Don't overwrite this function in sub-classes. Overwrite L</dump>
-instead.
-
-=item C<describe>
-
-Returns a human-readable description of the content. This should only
-be a single line without any markup.
-
-Sub-classes must overwrite this function.
-
-=item C<dump $object>
-
-Dumps the object as a hash/array tree and returns it by calling
-L<Rose::DB::Object::Helpers/as_tree>. The default implementation
-reloads the object first by calling L</reload_object>. It also only
-dumps the object itself, not any of the relationships, by calling
-C<as_tree> with the parameter C<max_depth =E<gt> 1>.
-
-Overwrite this in a sub-class if you need to dump more or differently
-(see L<SL::Clipboard::RequirementSpecItem> for an example).
-
-=item C<reload_object $object>
-
-Reloads C<$object> from the database and returns a new instance. Can
-be useful for sanitizing the object given to L</dump> before
-converting into a tree. It is used by the default implementation of
-L</dump>.
-
-=item C<to_object>
-
-Converts the dumped representation back to a Rose::DB::Object
-instance. Several columns of the newly created object are cleared by
-C<to_object> itself: the primary key columns (if any) and the columns
-C<itime> and C<mtime> (if the object has such columns).
-
-This function should not be overwritten by sub-classes. Instead,
-functions can overwrite C<_fix_object> which can be used for sanitizing
-the newly created object before handing it back to the caller.
-
-=item C<type>
-
-Returns the actual clipped type (e.g. C<RequirementSpecItem>). This is
-derived from the actual class name of C<$self>.
-
-=item C<_binary_column_names $class>
-
-Returns an array of column names that have a binary type. Useful for
-sub-classes which need to encode binary content in Base64 during
-C<dump>.
-
-=item C<_fix_object $object>
-
-This function is called by L</to_object> before the object is passed
-back to the caller. It does not do anything in the default
-implementation, but sub-classes are free to overwrite it if they need
-to sanitize the object. See L<SL::Clipboard::RequirementSpecItem> for
-an example.
-
-Its return value is ignored.
-
-=item C<_fix_tree $tree, $object>
-
-This function is called by L</as_tree> after dumping and before the
-object is stored during a copy operation. In the default
-implementation all primary key columns and the columns C<itime> and
-C<mtime> (if the object has such columns) are removed from the tree.
-Sub-classes are free to overwrite it if they need to sanitize the
-tree. See L<SL::Clipboard::RequirementSpecItem> for an example.
-
-C<$object> is just passed in for reference and should not be modified.
-
-Its return value is ignored.
-
-=back
-
-=head1 BUGS
-
-Nothing here yet.
-
-=head1 AUTHOR
-
-Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
-
-=cut
diff --git a/SL/Clipboard/RequirementSpecItem.pm b/SL/Clipboard/RequirementSpecItem.pm
deleted file mode 100644 (file)
index 85bd5d9..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package SL::Clipboard::RequirementSpecItem;
-
-use strict;
-
-use parent qw(SL::Clipboard::Base);
-
-use List::Util qw(sum);
-
-use SL::Common;
-use SL::Locale::String;
-
-sub dump {
-  my ($self, $object) = @_;
-
-  return $self->as_tree(_load_children($self->reload_object($object)), exclude => sub { ref($_[0]) !~ m/::RequirementSpecItem$/ });
-}
-
-sub describe {
-  my ($self) = @_;
-
-  my $item              = $self->content;
-  my $num_children      = @{ $item->{children} || [] };
-  my $num_grandchildren = sum map { scalar(@{ $_->{children} || [] }) } @{ $item->{children} || [] };
-
-  if ($item->{item_type} eq 'section') {
-    return t8('Requirement spec section #1 "#2" with #3 function blocks and a total of #4 sub function blocks; preamble: "#5"',
-              $item->{fb_number}, $item->{title}, $num_children, $num_grandchildren, Common::truncate($item->{description}, strip => 'full'));
-  } elsif ($item->{item_type} eq 'function-block') {
-    return t8('Requirement spec function block #1 with #2 sub function blocks; description: "#3"',
-              $item->{fb_number}, $num_children, Common::truncate($item->{description}, strip => 'full'));
-  } else {
-    return t8('Requirement spec sub function block #1; description: "#2"',
-              $item->{fb_number}, Common::truncate($item->{description}, strip => 'full'));
-  }
-}
-
-sub _load_children {
-  my ($object) = @_;
-
-  _load_children($_) for @{ $object->children };
-
-  return $object;
-}
-
-sub _fix_object {
-  my ($self, $object) = @_;
-
-  $object->$_(undef)     for qw(fb_number);
-  $self->_fix_object($_) for @{ $object->children || [] };
-}
-
-sub _fix_tree {
-  my ($self, $tree, $object) = @_;
-
-  delete @{ $tree }{ qw(id itime mtime parent_id position requirement_spec_id) };
-  $self->_fix_tree($_) for @{ $tree->{children} || [] };
-}
-
-1;
-__END__
-
-=pod
-
-=encoding utf8
-
-=head1 NAME
-
-SL::Clipboard::RequirementSpecItem - Clipboard specialization for
-SL::DB::RequirementSpecItem
-
-=head1 FUNCTIONS
-
-=over 4
-
-=item C<describe>
-
-Returns a human-readable description depending on the copied type
-(section, function block or sub function block).
-
-=item C<dump $object>
-
-This specialization reloads C<$object> from the database, loads all of
-its children (but only the other requirement spec items, no other
-relationships) and dumps it.
-
-=item C<_fix_object $object>
-
-Fixes C<$object> and all of its children by clearing certain columns
-like the position or function block numbers.
-
-=back
-
-=head1 BUGS
-
-Nothing here yet.
-
-=head1 AUTHOR
-
-Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
-
-=cut
diff --git a/SL/Clipboard/RequirementSpecPicture.pm b/SL/Clipboard/RequirementSpecPicture.pm
deleted file mode 100644 (file)
index 7a3757f..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package SL::Clipboard::RequirementSpecPicture;
-
-use strict;
-
-use parent qw(SL::Clipboard::Base);
-
-use SL::Common;
-use SL::Locale::String;
-use MIME::Base64;
-
-sub dump {
-  my ($self, $object) = @_;
-
-  $self->reload_object($object);
-
-  my $tree    = $self->as_tree($object, exclude => sub { ref($_[0]) !~ m/::RequirementSpecPicture$/ });
-  $tree->{$_} = encode_base64($tree->{$_}, '') for $self->_binary_column_names('SL::DB::RequirementSpecPicture');
-
-  return $tree;
-}
-
-sub describe {
-  my ($self) = @_;
-
-  return t8('Requirement spec picture "#1"', $self->content->{description} ? $self->content->{description} . ' (' . $self->content->{picture_file_name} . ')' : $self->content->{picture_file_name});
-}
-
-sub _fix_object {
-  my ($self, $object) = @_;
-
-  $object->$_(undef) for qw(number);
-  $object->$_(decode_base64($object->$_)) for $self->_binary_column_names('SL::DB::RequirementSpecPicture');
-
-  return $object;
-}
-
-1;
-__END__
-
-=pod
-
-=encoding utf8
-
-=head1 NAME
-
-SL::Clipboard::RequirementSpecPicture - Clipboard specialization for
-SL::DB::RequirementSpecPicture
-
-=head1 NOTES
-
-The underlying RDBO model contains binary columns, but binary data
-cannot be dumped as YAML. Therefore the binary content is encoded in
-Base64 in L</dump> and decoded back to binary form in L</_fix_object>.
-
-=head1 FUNCTIONS
-
-=over 4
-
-=item C<describe>
-
-Returns a human-readable description including the title and an
-excerpt of its content.
-
-=item C<dump $object>
-
-This specialization reloads C<$object> from the database, and dumps
-it. Binary columns are dumped encoded in Base64.
-
-=item C<_fix_object $object>
-
-Fixes C<$object> by clearing certain columns like the number. Also
-decodes binary columns from Base64 back to binary.
-
-=back
-
-=head1 BUGS
-
-Nothing here yet.
-
-=head1 AUTHOR
-
-Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
-
-=cut
diff --git a/SL/Clipboard/RequirementSpecTextBlock.pm b/SL/Clipboard/RequirementSpecTextBlock.pm
deleted file mode 100644 (file)
index 4b40a8e..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package SL::Clipboard::RequirementSpecTextBlock;
-
-use strict;
-
-use parent qw(SL::Clipboard::Base);
-
-use SL::Clipboard::RequirementSpecPicture;
-use SL::Common;
-use SL::Locale::String;
-
-sub dump {
-  my ($self, $object) = @_;
-
-  $self->reload_object($object);
-
-  my $tree          = $self->as_tree($object, exclude => sub { ref($_[0]) !~ m/::RequirementSpecTextBlock$/ });
-  $tree->{pictures} = [ map { SL::Clipboard::RequirementSpecPicture->new->dump($_) } @{ $object->pictures } ];
-
-  return $tree;
-}
-
-sub describe {
-  my ($self) = @_;
-
-  return t8('Requirement spec text block "#1"; content: "#2"', $self->content->{title}, Common::truncate($self->content->{text}, strip => 'full'));
-}
-
-sub _fix_object {
-  my ($self, $object) = @_;
-
-  $object->$_(undef) for qw(output_position position requirement_spec_id);
-
-  SL::Clipboard::RequirementSpecPicture->new->_fix_object($_) for @{ $object->pictures || [] };
-
-  return $object;
-}
-
-1;
-__END__
-
-=pod
-
-=encoding utf8
-
-=head1 NAME
-
-SL::Clipboard::RequirementSpecTextBlock - Clipboard specialization for
-SL::DB::RequirementSpecTextBlock
-
-=head1 FUNCTIONS
-
-=over 4
-
-=item C<describe>
-
-Returns a human-readable description including the title and an
-excerpt of its content.
-
-=item C<dump $object>
-
-This specialization reloads C<$object> from the database, loads all of
-its pictures and dumps it. The pictures are dumped using the clipboard
-specialization for it, L<SL::Clipboard::RequirementSpecPicture/dump>.
-
-=item C<_fix_object $object>
-
-Fixes C<$object> by clearing certain columns like the position. Lets
-pictures be fixed by the clipboard specialization for it,
-L<SL::Clipboard::RequirementSpecPicture/_fix_object>.
-
-=back
-
-=head1 BUGS
-
-Nothing here yet.
-
-=head1 AUTHOR
-
-Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
-
-=cut
diff --git a/SL/Controller/CsvImport/Clipboard/Base.pm b/SL/Controller/CsvImport/Clipboard/Base.pm
new file mode 100644 (file)
index 0000000..fc0a16a
--- /dev/null
@@ -0,0 +1,236 @@
+package SL::Clipboard::Base;
+
+use strict;
+
+use parent qw(Rose::Object);
+
+use Rose::Object::MakeMethods::Generic (
+  'scalar --get_set_init' => [ qw(content timestamp) ],
+);
+
+use Rose::DB::Object::Helpers ();
+
+sub init_timestamp { die "'timestamp' property not set"; }
+sub init_content   { die "'content' property not set";   }
+
+sub type {
+  my ($self_or_class) = @_;
+  return (split m/::/, ref($self_or_class) ? ref($self_or_class) : $self_or_class)[-1];
+}
+
+sub reload_object {
+  my ($self, $object) = @_;
+
+  return ref($object)->new(map { $_ => $object->$_ } $object->meta->primary_key)->load;
+}
+
+sub as_tree {
+  my ($self, $object, %params) = @_;
+
+  my $tree = Rose::DB::Object::Helpers::as_tree($object, %params);
+  $self->_fix_tree($tree, $object);
+  return $tree;
+}
+
+sub to_object {
+  my ($self) = @_;
+  my $object = Rose::DB::Object::Helpers::new_from_tree("SL::DB::" . $self->type, $self->content);
+
+  # Reset primary key columns and itime/mtime if the class supports it.
+  foreach ($object->meta->primary_key, 'itime', 'mtime') {
+    $object->$_(undef) if $object->can($_);
+  }
+
+  # Let sub classes fix the objects further.
+  $self->_fix_object($object);
+  return $object;
+}
+
+sub dump {
+  my ($self, $object) = @_;
+  return $self->as_tree($self->reload_object($object), max_depth => 1);
+}
+
+sub describe {
+  die "'describe' method not overwritten by derived class";
+}
+
+sub _fix_object {
+  my ($self, $object) = @_;
+  # To be overwritten by child classes.
+}
+
+sub _fix_tree {
+  my ($self, $tree, $object) = @_;
+
+  # Delete primary key columns and itime/mtime if the class supports it.
+  foreach ($object->meta->primary_key, 'itime', 'mtime') {
+    delete $tree->{$_} if $object->can($_);
+  }
+}
+
+sub _binary_column_names {
+  my ($self, $class) = @_;
+  return map  { $_->name }
+         grep { ref($_) =~ m/Pg::Bytea$/i }
+         @{ $class->meta->columns };
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Clipboard::Base - Base class for clipboard specialization classes
+
+=head1 SYNOPSIS
+
+See the synopsis of L<SL::Clipboard>.
+
+=head1 OVERVIEW
+
+This is a base class providing a lot of utility and
+defaults. Sub-classes must overwrite at least the function
+L</describe> but can overwrite others as well.
+
+Writing a specialized sub-class for a database type involves
+overwriting one or more functions. These are:
+
+=over 4
+
+=item * C<describe>
+
+Must be overwritten. Returns a human-readable description of the
+content. Should only be one line.
+
+=item * C<dump>
+
+Optional. Overwrite if sub-class needs to dump more/less than the
+implementation in this class dumps.
+
+=item * C<_fix_object>
+
+Optional. Overwrite if re-created Rose::DB::Object instances must be
+cleaned further before they're returned to the caller.
+
+=item * C<_fix_tree>
+
+Optional. Overwrite if the tree created during a copy operation of a
+Rose::DB::Object instance must be cleaned further before it's stored.
+
+=back
+
+You don't have to or should not overwrite the other functions:
+
+=over 4
+
+=item * C<as_tree>
+
+=item * C<reload_object>
+
+=item * C<to_object>
+
+=item * C<type>
+
+=back
+
+Don't forget to C<use> the specialized module here in Base!
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<as_tree $object, %params>
+
+A convenience function calling L<Rose::DB::Object::Helpers/as_tree>
+with C<$object> and C<%params> as parameters. Returns a hash/array
+reference tree of the function.
+
+Don't overwrite this function in sub-classes. Overwrite L</dump>
+instead.
+
+=item C<describe>
+
+Returns a human-readable description of the content. This should only
+be a single line without any markup.
+
+Sub-classes must overwrite this function.
+
+=item C<dump $object>
+
+Dumps the object as a hash/array tree and returns it by calling
+L<Rose::DB::Object::Helpers/as_tree>. The default implementation
+reloads the object first by calling L</reload_object>. It also only
+dumps the object itself, not any of the relationships, by calling
+C<as_tree> with the parameter C<max_depth =E<gt> 1>.
+
+Overwrite this in a sub-class if you need to dump more or differently
+(see L<SL::Clipboard::RequirementSpecItem> for an example).
+
+=item C<reload_object $object>
+
+Reloads C<$object> from the database and returns a new instance. Can
+be useful for sanitizing the object given to L</dump> before
+converting into a tree. It is used by the default implementation of
+L</dump>.
+
+=item C<to_object>
+
+Converts the dumped representation back to a Rose::DB::Object
+instance. Several columns of the newly created object are cleared by
+C<to_object> itself: the primary key columns (if any) and the columns
+C<itime> and C<mtime> (if the object has such columns).
+
+This function should not be overwritten by sub-classes. Instead,
+functions can overwrite C<_fix_object> which can be used for sanitizing
+the newly created object before handing it back to the caller.
+
+=item C<type>
+
+Returns the actual clipped type (e.g. C<RequirementSpecItem>). This is
+derived from the actual class name of C<$self>.
+
+=item C<_binary_column_names $class>
+
+Returns an array of column names that have a binary type. Useful for
+sub-classes which need to encode binary content in Base64 during
+C<dump>.
+
+=item C<_fix_object $object>
+
+This function is called by L</to_object> before the object is passed
+back to the caller. It does not do anything in the default
+implementation, but sub-classes are free to overwrite it if they need
+to sanitize the object. See L<SL::Clipboard::RequirementSpecItem> for
+an example.
+
+Its return value is ignored.
+
+=item C<_fix_tree $tree, $object>
+
+This function is called by L</as_tree> after dumping and before the
+object is stored during a copy operation. In the default
+implementation all primary key columns and the columns C<itime> and
+C<mtime> (if the object has such columns) are removed from the tree.
+Sub-classes are free to overwrite it if they need to sanitize the
+tree. See L<SL::Clipboard::RequirementSpecItem> for an example.
+
+C<$object> is just passed in for reference and should not be modified.
+
+Its return value is ignored.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
diff --git a/SL/Controller/CsvImport/Clipboard/RequirementSpecItem.pm b/SL/Controller/CsvImport/Clipboard/RequirementSpecItem.pm
new file mode 100644 (file)
index 0000000..85bd5d9
--- /dev/null
@@ -0,0 +1,101 @@
+package SL::Clipboard::RequirementSpecItem;
+
+use strict;
+
+use parent qw(SL::Clipboard::Base);
+
+use List::Util qw(sum);
+
+use SL::Common;
+use SL::Locale::String;
+
+sub dump {
+  my ($self, $object) = @_;
+
+  return $self->as_tree(_load_children($self->reload_object($object)), exclude => sub { ref($_[0]) !~ m/::RequirementSpecItem$/ });
+}
+
+sub describe {
+  my ($self) = @_;
+
+  my $item              = $self->content;
+  my $num_children      = @{ $item->{children} || [] };
+  my $num_grandchildren = sum map { scalar(@{ $_->{children} || [] }) } @{ $item->{children} || [] };
+
+  if ($item->{item_type} eq 'section') {
+    return t8('Requirement spec section #1 "#2" with #3 function blocks and a total of #4 sub function blocks; preamble: "#5"',
+              $item->{fb_number}, $item->{title}, $num_children, $num_grandchildren, Common::truncate($item->{description}, strip => 'full'));
+  } elsif ($item->{item_type} eq 'function-block') {
+    return t8('Requirement spec function block #1 with #2 sub function blocks; description: "#3"',
+              $item->{fb_number}, $num_children, Common::truncate($item->{description}, strip => 'full'));
+  } else {
+    return t8('Requirement spec sub function block #1; description: "#2"',
+              $item->{fb_number}, Common::truncate($item->{description}, strip => 'full'));
+  }
+}
+
+sub _load_children {
+  my ($object) = @_;
+
+  _load_children($_) for @{ $object->children };
+
+  return $object;
+}
+
+sub _fix_object {
+  my ($self, $object) = @_;
+
+  $object->$_(undef)     for qw(fb_number);
+  $self->_fix_object($_) for @{ $object->children || [] };
+}
+
+sub _fix_tree {
+  my ($self, $tree, $object) = @_;
+
+  delete @{ $tree }{ qw(id itime mtime parent_id position requirement_spec_id) };
+  $self->_fix_tree($_) for @{ $tree->{children} || [] };
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Clipboard::RequirementSpecItem - Clipboard specialization for
+SL::DB::RequirementSpecItem
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<describe>
+
+Returns a human-readable description depending on the copied type
+(section, function block or sub function block).
+
+=item C<dump $object>
+
+This specialization reloads C<$object> from the database, loads all of
+its children (but only the other requirement spec items, no other
+relationships) and dumps it.
+
+=item C<_fix_object $object>
+
+Fixes C<$object> and all of its children by clearing certain columns
+like the position or function block numbers.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
diff --git a/SL/Controller/CsvImport/Clipboard/RequirementSpecPicture.pm b/SL/Controller/CsvImport/Clipboard/RequirementSpecPicture.pm
new file mode 100644 (file)
index 0000000..7a3757f
--- /dev/null
@@ -0,0 +1,84 @@
+package SL::Clipboard::RequirementSpecPicture;
+
+use strict;
+
+use parent qw(SL::Clipboard::Base);
+
+use SL::Common;
+use SL::Locale::String;
+use MIME::Base64;
+
+sub dump {
+  my ($self, $object) = @_;
+
+  $self->reload_object($object);
+
+  my $tree    = $self->as_tree($object, exclude => sub { ref($_[0]) !~ m/::RequirementSpecPicture$/ });
+  $tree->{$_} = encode_base64($tree->{$_}, '') for $self->_binary_column_names('SL::DB::RequirementSpecPicture');
+
+  return $tree;
+}
+
+sub describe {
+  my ($self) = @_;
+
+  return t8('Requirement spec picture "#1"', $self->content->{description} ? $self->content->{description} . ' (' . $self->content->{picture_file_name} . ')' : $self->content->{picture_file_name});
+}
+
+sub _fix_object {
+  my ($self, $object) = @_;
+
+  $object->$_(undef) for qw(number);
+  $object->$_(decode_base64($object->$_)) for $self->_binary_column_names('SL::DB::RequirementSpecPicture');
+
+  return $object;
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Clipboard::RequirementSpecPicture - Clipboard specialization for
+SL::DB::RequirementSpecPicture
+
+=head1 NOTES
+
+The underlying RDBO model contains binary columns, but binary data
+cannot be dumped as YAML. Therefore the binary content is encoded in
+Base64 in L</dump> and decoded back to binary form in L</_fix_object>.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<describe>
+
+Returns a human-readable description including the title and an
+excerpt of its content.
+
+=item C<dump $object>
+
+This specialization reloads C<$object> from the database, and dumps
+it. Binary columns are dumped encoded in Base64.
+
+=item C<_fix_object $object>
+
+Fixes C<$object> by clearing certain columns like the number. Also
+decodes binary columns from Base64 back to binary.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut
diff --git a/SL/Controller/CsvImport/Clipboard/RequirementSpecTextBlock.pm b/SL/Controller/CsvImport/Clipboard/RequirementSpecTextBlock.pm
new file mode 100644 (file)
index 0000000..4b40a8e
--- /dev/null
@@ -0,0 +1,81 @@
+package SL::Clipboard::RequirementSpecTextBlock;
+
+use strict;
+
+use parent qw(SL::Clipboard::Base);
+
+use SL::Clipboard::RequirementSpecPicture;
+use SL::Common;
+use SL::Locale::String;
+
+sub dump {
+  my ($self, $object) = @_;
+
+  $self->reload_object($object);
+
+  my $tree          = $self->as_tree($object, exclude => sub { ref($_[0]) !~ m/::RequirementSpecTextBlock$/ });
+  $tree->{pictures} = [ map { SL::Clipboard::RequirementSpecPicture->new->dump($_) } @{ $object->pictures } ];
+
+  return $tree;
+}
+
+sub describe {
+  my ($self) = @_;
+
+  return t8('Requirement spec text block "#1"; content: "#2"', $self->content->{title}, Common::truncate($self->content->{text}, strip => 'full'));
+}
+
+sub _fix_object {
+  my ($self, $object) = @_;
+
+  $object->$_(undef) for qw(output_position position requirement_spec_id);
+
+  SL::Clipboard::RequirementSpecPicture->new->_fix_object($_) for @{ $object->pictures || [] };
+
+  return $object;
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Clipboard::RequirementSpecTextBlock - Clipboard specialization for
+SL::DB::RequirementSpecTextBlock
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<describe>
+
+Returns a human-readable description including the title and an
+excerpt of its content.
+
+=item C<dump $object>
+
+This specialization reloads C<$object> from the database, loads all of
+its pictures and dumps it. The pictures are dumped using the clipboard
+specialization for it, L<SL::Clipboard::RequirementSpecPicture/dump>.
+
+=item C<_fix_object $object>
+
+Fixes C<$object> by clearing certain columns like the position. Lets
+pictures be fixed by the clipboard specialization for it,
+L<SL::Clipboard::RequirementSpecPicture/_fix_object>.
+
+=back
+
+=head1 BUGS
+
+Nothing here yet.
+
+=head1 AUTHOR
+
+Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
+
+=cut