Bug 12404

Summary: CSV profiles improvements (concatenations, substrings, conditions...)
Product: Koha Reporter: Jonathan Druart <jonathan.druart>
Component: ToolsAssignee: Jonathan Druart <jonathan.druart>
Status: CLOSED FIXED QA Contact: Testopia <testopia>
Severity: enhancement    
Priority: P5 - low CC: eric.begin, f.demians, fridolin.somers, julian.maurice, sandboxes, scourret, tomascohen
Version: MainKeywords: Manual
Hardware: All   
OS: All   
See Also: http://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=14757
https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=33659
Change sponsored?: --- Patch complexity: ---
Documentation contact: Documentation submission:
Text to go in the release notes:
Version(s) released in:
Circulation function:
Bug Depends on: 13190    
Bug Blocks: 33659    
Attachments: Bug 12404: Add UT for marcrecord2csv
Bug 12404: Add new unit tests
Bug 12404: Allow TT tags for csv profiles
Bug 12404: tools/export.pl allows CSV export
Bug 12404: Add some documentation in the help page.
Bug 12404: Add UT for marcrecord2csv
Bug 12404: Add new unit tests
Bug 12404: Allow TT tags for csv profiles
Bug 12404: tools/export.pl allows CSV export
Bug 12404: Add some documentation in the help page.
Bug 12404: Allow equal sign '=' in the TT directive
Bug 12404: FIX documentation to join subfield
Bug 12841: aqorders fields should not be mapped
Bug 12980: GetHistory does useless processing
Bug 12852: The "preview" param can be removed serials/claims.pl
Bug 12852: Some unused stuffs more
Bug 12852: C4::Bookseller should not be imported anymore
Bug 12123 - [Alternative patch] HTML notices can break the notice viewer
DBRev 3.19.00.000: keep moving
Bug 12404: tools/export.pl allows CSV export
Bug 12404: Add some documentation in the help page.
Bug 12404: Allow equal sign '=' in the TT directive
Bug 12404: FIX documentation to join subfield
Bug 12404: Add UT for marcrecord2csv
Bug 12404: Add new unit tests
Bug 12404: Allow TT tags for csv profiles
Bug 12404: tools/export.pl allows CSV export
Bug 12404: Add some documentation in the help page.
Bug 12404: Allow equal sign '=' in the TT directive
Bug 12404: FIX documentation to join subfield
[PASSED QA] Bug 12404: Add UT for marcrecord2csv
[PASSED QA] Bug 12404: Add new unit tests
[PASSED QA] Bug 12404: Allow TT tags for csv profiles
[PASSED QA] Bug 12404: tools/export.pl allows CSV export
[PASSED QA] Bug 12404: Add some documentation in the help page.
[PASSED QA] Bug 12404: Allow equal sign '=' in the TT directive
[PASSED QA] Bug 12404: FIX documentation to join subfield

Description Jonathan Druart 2014-06-11 09:48:14 UTC
The goal of this development is to provide a better flexibility on creating a CSV profile.
Currently it is not possible to:
  - Concatenate specific subfields into a csv column
  - Display a field/subfield using a condition
  - Extract a substring of a subfield value
and a lot of other actions.
Comment 1 Jonathan Druart 2014-06-11 09:51:31 UTC Comment hidden (obsolete)
Comment 2 Jonathan Druart 2014-06-11 09:51:35 UTC Comment hidden (obsolete)
Comment 3 Jonathan Druart 2014-06-11 09:51:39 UTC Comment hidden (obsolete)
Comment 4 Jonathan Druart 2014-06-11 09:51:44 UTC Comment hidden (obsolete)
Comment 5 Jonathan Druart 2014-06-11 09:51:49 UTC Comment hidden (obsolete)
Comment 6 Frédéric Demians 2014-11-01 15:59:20 UTC
There is a conflict on circ/circulation.tt. Could you fix it?
Comment 7 Jonathan Druart 2014-11-03 17:27:45 UTC Comment hidden (obsolete)
Comment 8 Jonathan Druart 2014-11-03 17:27:48 UTC Comment hidden (obsolete)
Comment 9 Jonathan Druart 2014-11-03 17:27:51 UTC Comment hidden (obsolete)
Comment 10 Jonathan Druart 2014-11-03 17:27:54 UTC Comment hidden (obsolete)
Comment 11 Jonathan Druart 2014-11-03 17:27:57 UTC Comment hidden (obsolete)
Comment 12 Jonathan Druart 2014-11-03 17:28:53 UTC
Bug 10703 introduces a big regression, see bug 13190.
Has to be apply before this patch set.
Comment 13 Frédéric Demians 2014-11-03 21:20:28 UTC
For me, there is a design error in you dev. You send to TT a representation of
MARC record which is not correct. You loose the field order in the record, and
subfields order inside each field. The fields object sent to TT is a hashref of
arrayref of hashref of array. The valid representation is an arrayref (1 per
tag) of arrayref (1 per subfield) of arrayref (1 per pair letter/value).

So for example (bad example), if you have:

245 ## - TITLE STATEMENT
  a Title My first title
  b Remainder of title My remainder
  a Title My second title

With your represenation, you can't know anymore that the second $a comes after
$b. In MARC21, it's important since separators are in the subfield content
itself. Your 245 field will be represented like that:

  245 => {
    'a' => [
      'My first title',
      'My second title'
    ],
    'b' => [
      'My remainder'
    ]
  }
Comment 14 Jonathan Druart 2014-11-04 08:54:39 UTC
(In reply to Frédéric Demians from comment #13)
> For me, there is a design error in you dev. You send to TT a representation
> of
> MARC record which is not correct. You loose the field order in the record,
> and
> subfields order inside each field. The fields object sent to TT is a hashref
> of
> arrayref of hashref of array. The valid representation is an arrayref (1 per
> tag) of arrayref (1 per subfield) of arrayref (1 per pair letter/value).

It's easier to manipulate hashref than arrayref in this case.
If you want to access to the $b subfield, you don't need to process all subfields.
Could you give me an where this structure would be a problem for a CSV export?
Comment 15 Frédéric Demians 2014-11-04 10:04:27 UTC
(In reply to Jonathan Druart from comment #14)

> It's easier to manipulate hashref than arrayref in this case.  If you want to
> access to the $b subfield, you don't need to process all subfields.  Could
> you give me an where this structure would be a problem for a CSV export?

I agree. I have seen TT syntax you can get: fields.245, fields.245.a. And then
[% FOREACH %] on hashref keys... It's elegant, but it is also a trap.

In MARC21, you can have this kind of title field:

  245 14 $a The phonics basic skills workbook. 
         $n Part 1, 
         $p Reading readiness
         $h [videorecording] : 
         $b preparing for kindergarten.

which should be rendered: The phonics basic skills workbook. Part 1, Reading
readiness [videorecording] : preparing for kindergarten.

With your field representation, you loose the subfield order: a, n, p, h, and
b. There is chance a TT construction will try to put $b just after $a, which is
wrong.
Comment 16 Jonathan Druart 2014-11-04 12:22:20 UTC
(In reply to Frédéric Demians from comment #15)
> With your field representation, you loose the subfield order: a, n, p, h, and
> b. There is chance a TT construction will try to put $b just after $a, which
> is
> wrong.

I am not sure to understand the problem.
In our case (CSV), you will set something like:
===
Title=[% FOREACH field IN fields.245 %][% field.a.0 %] [% field.a.n %] [% field.a.p %] [% field.a.h %] [% field.a.b %][% END %]
===

Since it's not possible to display an entire field and you need to list the subfields you want to display, the order is not a problem.

But maybe I am missing something.

IMO this way allows us a very good flexibility (since we can use the TT methods) with a minimum of code.
Comment 17 Frédéric Demians 2014-11-04 12:54:47 UTC
> I am not sure to understand the problem.
> In our case (CSV), you will set something like:
> ===
> Title=[% FOREACH field IN fields.245 %][% field.a.0 %] [% field.a.n %] [%
> field.a.p %] [% field.a.h %] [% field.a.b %][% END %]
> ===
> 
> Since it's not possible to display an entire field and you need to list the
> subfields you want to display, the order is not a problem.

Let suppose than another valid 245 field contains (from LoC MARC21 format):

245 00 $a Love from Joy :
       $b letters from a farmer’s wife.
       $n Part III,
       $p 1987-1995, At the bungalow.

It must be displayed (and exported): Love from Joy : letters from a farmer’s
wife. Part III, 1987-1995, At the bungalow.

With you TT (minus typo): Love from Joy : Part III, 1987-1995, At the bungalow.
letters from a farmer's wife.

You can't have an unique TT for all 245 subfields combinations.

> IMO this way allows us a very good flexibility (since we can use the TT
> methods) with a minimum of code.

It still possible. Fields order isn't that important, but sufields is. You
could build an hashref of arrayref of arrayref. For a $record containing a
MARC::Record (its a variation of your code):

my $fields;
for my $field ( $record->fields ) {
    my $fieldtag = $field->tag;
    my $values;
    if ( $field->is_control_field ) {
        $values = $field->data();
    } else {
        $values->{indicator}{1} = $field->indicator(1);
        $values->{indicator}{2} = $field->indicator(2);
        for ( $field->subfields ) {
            my ($letter, $value) = @$_;
            push @{ $values->{subf} }, [ $letter => $value ];
        }
    }
    # We force the key as an integer (trick for 00X and OXX fields)
    push @{ $fields->{fields}{0+$fieldtag} }, $values;
}

And then to display the first 245 field, all subfields in order:

[% FOREACH s IN fields.245.first.subf %][% s.1 %] [% END %]
Comment 18 Jonathan Druart 2014-11-04 13:21:28 UTC
(In reply to Frédéric Demians from comment #17)
> It still possible. Fields order isn't that important, but sufields is. You
> could build an hashref of arrayref of arrayref. For a $record containing a
> MARC::Record (its a variation of your code):

I accept, of course, all kinds of followup to improve this enhancement :)
Comment 19 Frédéric Demians 2014-11-04 14:03:07 UTC
(In reply to Jonathan Druart from comment #18)

> I accept, of course, all kinds of followup to improve this enhancement :)

I don't say no :-) I've even tried.

But I'me encountering now an issue with the mix in the code between previous syntax and TT syntax. I can't have a TT directive containing a = sign. For example:

  [% IF s.0 == 'b' %]
  [% SET letter = s.0 %]

The = sign is used to split the CSV profile here:

# Separating the marcfields from the user-supplied headers
    my @csv_structures;
    foreach (@marcfieldsarray) {
        my @result = split('=', $_);
        my $content = ( @result == 2 )
            ? $result[1]
            : $result[0];

So it breaks valid TT code.
Comment 20 Jonathan Druart 2014-11-05 16:12:14 UTC Comment hidden (obsolete)
Comment 21 Jonathan Druart 2014-11-05 16:12:58 UTC
(In reply to Frédéric Demians from comment #19)
> (In reply to Jonathan Druart from comment #18)
> 
> > I accept, of course, all kinds of followup to improve this enhancement :)
> 
> I don't say no :-) I've even tried.
> 
> But I'me encountering now an issue with the mix in the code between previous
> syntax and TT syntax. I can't have a TT directive containing a = sign.

The last patch should allow you to do what you want.
Comment 22 Frédéric Demians 2014-11-05 17:52:20 UTC
(In reply to Jonathan Druart from comment #21)

> The last patch should allow you to do what you want.

It works if I have something like that (with an arrayref of subfields):

Title=[% FOREACH s IN fields.200.first.subfields %][% IF s.0 == "a" %][% s.1 %][% END %][% END %]

It doesn't work with:

[% FOREACH s IN fields.200.first.subfields %][% IF s.0 == "a" %][% s.1 %][% END %][% END %]

The editing workflow is really awkward. After saving a profile, you have to do several clicks, to return on edit the CSV profile. And worst, you haven't any feedback, syntax verification, or a visual feedback. You have to switch to Download cart to see the result. When there is no result, you can surmise you have done something wrong.
Comment 23 Jonathan Druart 2014-11-06 09:33:51 UTC
(In reply to Frédéric Demians from comment #22)
> (In reply to Jonathan Druart from comment #21)
> 
> > The last patch should allow you to do what you want.
> 
> It works if I have something like that (with an arrayref of subfields):
> 
> Title=[% FOREACH s IN fields.200.first.subfields %][% IF s.0 == "a" %][% s.1
> %][% END %][% END %]
> 
> It doesn't work with:
> 
> [% FOREACH s IN fields.200.first.subfields %][% IF s.0 == "a" %][% s.1 %][%
> END %][% END %]

Hum, yes. I have no solution for this case. It's a limitation.

> The editing workflow is really awkward. After saving a profile, you have to
> do several clicks, to return on edit the CSV profile. And worst, you haven't
> any feedback, syntax verification, or a visual feedback. You have to switch
> to Download cart to see the result. When there is no result, you can surmise
> you have done something wrong.

Yes, I totally agree...
But it is not in the scope of this enhancement.
Comment 24 Francois Charbonnier 2014-11-11 17:44:06 UTC
I tested this with two CSV profils :

Without TT methods
1. Personal name=200|Entry element=210$a|300|009 -> It works

With TT methods
2. 245i1=[% fields.245.0.indicator.2 %]|245ac=[% FOREACH field IN fields.245 %] [% field.a.0 %] [% field.c.0 %] [% END %] | Subject=[% FOREACH field IN fields.650 %][% IF field.indicator.2 %][% field.a.0 %] [% END %][% END %] | Language=[% fields.008.0.substr( 28, 3 ) %] |  Title=[% IF fields.100.0.indicator.1 %][% fields.245.0.a.0 %][% END %]

I used the examples in the help section for the last one. I also works BUT I had to correct one method.

The help section says :
Display all 245$a and 245$c into the same column:
[% FOREACH field IN fields.245 %] [% field.a %] [% field.c %] [% END %] 

but it doesn't work. I think it should be corrected by :
[% FOREACH field IN fields.245 %] [% field.a.0 %] [% field.c.0 %] [% END %]
which works.

Since I'm not familiar with template toolkit, I let you confirm it's correct.

Once the documentation is corrected I'll test it again to sign it off!
Comment 25 Jonathan Druart 2014-11-12 10:16:43 UTC Comment hidden (obsolete)
Comment 26 Jonathan Druart 2014-11-12 10:17:12 UTC
(In reply to Francois Charbonnier from comment #24)

I updated the documentation in the last patch!
Comment 27 Biblibre Sandboxes 2014-12-11 09:02:27 UTC
Patch tested with a sandbox, by scourret@gmail.com <2>
Comment 28 Biblibre Sandboxes 2014-12-11 09:03:38 UTC Comment hidden (obsolete)
Comment 29 Biblibre Sandboxes 2014-12-11 09:03:41 UTC Comment hidden (obsolete)
Comment 30 Biblibre Sandboxes 2014-12-11 09:03:43 UTC Comment hidden (obsolete)
Comment 31 Biblibre Sandboxes 2014-12-11 09:03:46 UTC Comment hidden (obsolete)
Comment 32 Biblibre Sandboxes 2014-12-11 09:03:49 UTC Comment hidden (obsolete)
Comment 33 Biblibre Sandboxes 2014-12-11 09:03:52 UTC Comment hidden (obsolete)
Comment 34 Biblibre Sandboxes 2014-12-11 09:03:55 UTC Comment hidden (obsolete)
Comment 35 Jonathan Druart 2014-12-11 09:40:20 UTC
Problem with sandbox, back to needs signoff.
Comment 36 Jonathan Druart 2014-12-11 09:57:53 UTC Comment hidden (obsolete)
Comment 37 Jonathan Druart 2014-12-11 09:57:56 UTC Comment hidden (obsolete)
Comment 38 Jonathan Druart 2014-12-11 09:58:00 UTC Comment hidden (obsolete)
Comment 39 Jonathan Druart 2014-12-11 09:58:03 UTC Comment hidden (obsolete)
Comment 40 Jonathan Druart 2014-12-11 09:59:25 UTC
patches rebased, conflicts with bug 13308 fixed.
Comment 41 Courret 2014-12-11 13:48:55 UTC
Patch works for displaying subfields in the same column. Also works for displaying the first subfield for a field if specified indicator  for field  is present. Can display a subfield if an indicator is set. 
So most of the features are working without trouble. 
But it looks like I’m unable to extract a substring from a field (typically, UNIMARC language field from 100$a – character 22-24). May someone test it ?
Comment 42 Jonathan Druart 2014-12-11 13:59:31 UTC
(In reply to Courret from comment #41)
> But it looks like I’m unable to extract a substring from a field (typically,
> UNIMARC language field from 100$a – character 22-24). May someone test it ?

Did you try something like

  [% fields.100.0.a.0.substr( 22, 3 ) %]

?
Comment 43 Courret 2014-12-11 15:40:33 UTC
(In reply to Jonathan Druart from comment #42)
> (In reply to Courret from comment #41)
> > But it looks like I’m unable to extract a substring from a field (typically,
> > UNIMARC language field from 100$a – character 22-24). May someone test it ?
> 
> Did you try something like
> 
>   [% fields.100.0.a.0.substr( 22, 3 ) %]
> 
> ?

It works ! Seems good. For me, as far as I have tested, every feature is OK and patch can be validated.
Comment 44 Jonathan Druart 2014-12-15 10:53:00 UTC Comment hidden (obsolete)
Comment 45 Jonathan Druart 2014-12-15 10:53:04 UTC Comment hidden (obsolete)
Comment 46 Jonathan Druart 2014-12-15 10:53:09 UTC Comment hidden (obsolete)
Comment 47 Jonathan Druart 2014-12-15 10:53:13 UTC Comment hidden (obsolete)
Comment 48 Jonathan Druart 2014-12-15 10:53:16 UTC Comment hidden (obsolete)
Comment 49 Jonathan Druart 2014-12-15 10:53:20 UTC Comment hidden (obsolete)
Comment 50 Jonathan Druart 2014-12-15 10:53:26 UTC Comment hidden (obsolete)
Comment 51 Kyle M Hall (khall) 2015-01-09 12:47:51 UTC
Created attachment 35048 [details] [review]
[PASSED QA] Bug 12404: Add UT for marcrecord2csv

Verify that these unit tests pass before any changes and after applying
all patches.

Signed-off-by: Courret <scourret@gmail.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Comment 52 Kyle M Hall (khall) 2015-01-09 12:47:58 UTC
Created attachment 35049 [details] [review]
[PASSED QA] Bug 12404: Add new unit tests

These unit tests reflect the changes done in next patches.

Signed-off-by: Courret <scourret@gmail.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Comment 53 Kyle M Hall (khall) 2015-01-09 12:48:01 UTC
Created attachment 35050 [details] [review]
[PASSED QA] Bug 12404: Allow TT tags for csv profiles

This patch is the main patch. It contains the changes in
C4::Record::marcrecord2csv.

The goal of this development is to provide a better flexibility on
creating a CSV profile.
Currently it is not possible to:
- Concatenate specific subfields into a csv column
- Display a field/subfield using a condition
- Extract a substring of a subfield value
and a lot of other actions.

This patch allows to write Template Toolkit code and to extract only
data you want.

See the help page for more information (in next patch).

Test plan:
Create some CSV profiles (MARC, not SQL) using some TT methods.
Use the basket export and the export tool and verify the CSV file
generated is what you expected.

Signed-off-by: Courret <scourret@gmail.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Comment 54 Kyle M Hall (khall) 2015-01-09 12:48:04 UTC
Created attachment 35051 [details] [review]
[PASSED QA] Bug 12404: tools/export.pl allows CSV export

Signed-off-by: Courret <scourret@gmail.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Comment 55 Kyle M Hall (khall) 2015-01-09 12:48:07 UTC
Created attachment 35052 [details] [review]
[PASSED QA] Bug 12404: Add some documentation in the help page.

Signed-off-by: Courret <scourret@gmail.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Comment 56 Kyle M Hall (khall) 2015-01-09 12:48:09 UTC
Created attachment 35053 [details] [review]
[PASSED QA] Bug 12404: Allow equal sign '=' in the TT directive

Signed-off-by: Courret <scourret@gmail.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Comment 57 Kyle M Hall (khall) 2015-01-09 12:48:12 UTC
Created attachment 35054 [details] [review]
[PASSED QA] Bug 12404: FIX documentation to join subfield

To display all subfields, the join TT method must be used.

Signed-off-by: Courret <scourret@gmail.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Comment 58 Tomás Cohen Arazi (tcohen) 2015-04-27 16:39:55 UTC
Feature pushed to master.

Thanks Jonathan!
Comment 59 Katrin Fischer 2020-12-17 09:32:52 UTC
Appears sadly to be undocumented!
Comment 60 Jonathan Druart 2020-12-17 13:47:20 UTC
(In reply to Katrin Fischer from comment #59)
> Appears sadly to be undocumented!

I can be retrieved from
  commit 57b48279bdf781e74329735fc3f5068da8398ac4
  Bug 12404: Add some documentation in the help page.
Comment 61 Katrin Fischer 2023-05-03 11:27:11 UTC
I tried to use an example from the old help file, but I got:

+  <li>Display all 245$a and 245$c into the same column:
+    <p>
+    [% FOREACH field IN fields.245 %]
+        [% field.a %] [% field.c %]
+    [% END %]
+    </p>
+  </li>

Titel=[% FOREACH field IN fields.245 %][% field.a %] [% field.b %] [% field.n %] [% field.p %] [% field.c %][% END %]

ARRAY(0x5642032a0638) ARRAY(0x564202a832f0) ARRAY(0x564203388d48) ARRAY(0x5642033cc020) ARRAY(0x564203439cc0)
Comment 62 Jonathan Druart 2023-05-10 07:17:25 UTC
(In reply to Katrin Fischer from comment #61)
> I tried to use an example from the old help file, but I got:
> 
> +  <li>Display all 245$a and 245$c into the same column:
> +    <p>
> +    [% FOREACH field IN fields.245 %]
> +        [% field.a %] [% field.c %]
> +    [% END %]
> +    </p>
> +  </li>
> 
> Titel=[% FOREACH field IN fields.245 %][% field.a %] [% field.b %] [%
> field.n %] [% field.p %] [% field.c %][% END %]
> 
> ARRAY(0x5642032a0638) ARRAY(0x564202a832f0) ARRAY(0x564203388d48)
> ARRAY(0x5642033cc020) ARRAY(0x564203439cc0)

field.a contains the list of a's.
The following works:
Title=[% FOREACH field IN fields.245 %][% field.a.0 %] [% field.b.0 %] [% field.n.0 %] [% field.p.0 %] [% field.c.0 %][% END %]
Comment 63 Jonathan Druart 2023-05-10 07:21:12 UTC
(In reply to Jonathan Druart from comment #62)
> (In reply to Katrin Fischer from comment #61)
> > I tried to use an example from the old help file, but I got:
> > 
> > +  <li>Display all 245$a and 245$c into the same column:
> > +    <p>
> > +    [% FOREACH field IN fields.245 %]
> > +        [% field.a %] [% field.c %]
> > +    [% END %]
> > +    </p>
> > +  </li>
> > 
> > Titel=[% FOREACH field IN fields.245 %][% field.a %] [% field.b %] [%
> > field.n %] [% field.p %] [% field.c %][% END %]
> > 
> > ARRAY(0x5642032a0638) ARRAY(0x564202a832f0) ARRAY(0x564203388d48)
> > ARRAY(0x5642033cc020) ARRAY(0x564203439cc0)
> 
> field.a contains the list of a's.
> The following works:
> Title=[% FOREACH field IN fields.245 %][% field.a.0 %] [% field.b.0 %] [%
> field.n.0 %] [% field.p.0 %] [% field.c.0 %][% END %]

Ha, there is a follow-up commit:
  commit c33c56028fb39a464a780b2a22e95af0da73585f
  Bug 12404: FIX documentation to join subfield

The correct syntax is:
   <li>Display all 245$a and 245$c into the same column:
     <p>
     [% FOREACH field IN fields.245 %]
       [% field.a.join(' ') %] [% field.c.join(' ') %]
     [% END %]
     </p>
   </li>