From ac62a4cc4a1881277c1ad9f11db65f8d0faaf0e8 Mon Sep 17 00:00:00 2001
From: Gerhard Gonter <ggonter@gmail.com>
Date: Wed, 1 Jan 2014 00:29:08 +0100
Subject: [PATCH] converting content lines in parsend and array format back and
 forth seem to work now

---
 perl/Gnome-Tomboy/lib/Tomboy/Note/Simple.pm | 310 ++++++++++++++++++--
 perl/Gnome-Tomboy/s1.pl                     |  54 +++-
 2 files changed, 335 insertions(+), 29 deletions(-)

diff --git a/perl/Gnome-Tomboy/lib/Tomboy/Note/Simple.pm b/perl/Gnome-Tomboy/lib/Tomboy/Note/Simple.pm
index 048bc7f..a1bb0e3 100755
--- a/perl/Gnome-Tomboy/lib/Tomboy/Note/Simple.pm
+++ b/perl/Gnome-Tomboy/lib/Tomboy/Note/Simple.pm
@@ -4,10 +4,14 @@ package Tomboy::Note::Simple;
 
 =head1 NAME
 
-  Tomboy::Note::Simple;
+  Tomboy::Note::Simple
 
 =head1 SYNOPSIS
 
+  my $note= new Tomboy::Note::Simple (options => values);
+
+=head2 parsing
+
   # version 1
   my $n1= parse Tomboy::Note::Simple ($note_fnm);
 
@@ -19,7 +23,28 @@ package Tomboy::Note::Simple;
 Simple abstraction for notes written with Gnome's Tomboy.
 
 The script uses XML::Parser in Tree style and uses it's parse
-tree as the note's content datastructure.
+tree as the note's content datastructure (stored in "text").
+
+=head1 BUGS
+
+This module consists of originally two different ones, they are not
+completely consistent.  The difference is how the content is stored.
+
+In a .note file, contents looks like this:
+
+ <text xml:space="preserve"><note-content version="0.1">title line
+ content line2
+ ...
+ last content last</note-content></text>
+
+ * Parser: based on XML::Parser, stores contents in
+   @nc= @{$note->{'text'}->[2]} which represents the the <note-content>
+   element, the first text-part starts at $nc[2]
+
+ * Generator: contents (the stuff *in* the "note-content" element)
+   is put into "lines" the first line, however, is stored in 'title'.
+
+That should be further unified.
 
 =cut
 
@@ -30,40 +55,69 @@ use JSON;
 use Data::Dumper;
 $Data::Dumper::Indent= 1;
 
+use Tomboy;
+use Util::XML_Parser_Tree;
+
 my %fields=
 (
   'title' => {},
   'last-change-date' => {},
   'last-metadata-change-date' => {},
   'create-date' => {},
-  'cursor-position' => {},
-  'selection-bound-position' => {},
-  'width' => {},
-  'height' => {},
-  'x' => {},
-  'y' => {},
-  'open-on-startup' => {},
+  'cursor-position'          => { 'default' => 0 },
+  'selection-bound-position' => { 'default' => -1, 'supress' => 1 },
+  'width'  => { 'default' => 450 },
+  'height' => { 'default' => 360 },
+  'x'      => { 'default' => 0 },
+  'y'      => { 'default' => 0 },
+  'open-on-startup' => { 'default' => 'False' },
 );
 
+my @fields_date=     qw( last-change-date last-metadata-change-date create-date );
+my @fields_default1= qw( cursor-position selection-bound-position width height x y );
+my @fields_default2= qw( open-on-startup );
+my @fields_seq1=     (@fields_date, @fields_default1);
+my @fields_seq2=     (@fields_default2);
+
+my ($s_text, $e_text)= ('<text xml:space="preserve">', '</text>');
+my ($s_note_content, $e_note_content)= ('<note-content version="0.1">', '</note-content>');
+
 sub new
 {
   my $class= shift;
-  my %par= @_;
-
-  my $note= {};
-  bless $note, $class;
 
-  foreach my $par (keys %par)
+  my $title= 'New Note ' . Tomboy::ts_ISO ();
+  my $note=
   {
-    $note->{$par}= $par{$par};
-  }
+    'lines' => [ $title ],
+    'title' => $title,
+  };
+  foreach my $f (@fields_date)     { $note->{$f}= Tomboy::ts_ISO() }
+  foreach my $f (@fields_default1) { $note->{$f}= $fields{'default'} }
+
+  bless $note, $class;
+  $note->set (@_);
 
   $note;
 }
 
+sub set
+{
+  my $note= shift;
+  my %par= @_;
+
+  foreach my $par (keys %par) { $note->{$par}= $par{$par} }
+  1;
+}
+
+=head1 Group1: Parsing
+
+=cut
+
 sub empty_text
 {
   my $note= shift;
+  my $title= shift || 'empty text';
 
   $note->{'text'}= [
     {
@@ -75,9 +129,11 @@ sub empty_text
         'version' => '0.1'
       },
       0,
-      'empty text'
+      $title,
     ]
   ];
+  $note->{'title'}= $title;
+  $note->{'lines'}= [ $title ];
 
   1;
 }
@@ -105,8 +161,16 @@ sub parse
 
   my $p= new XML::Parser (Style => 'Tree');
   # print "p: ", Dumper ($p);
-  my $l1= $p->parsefile($fnm, ErrorContext => 3);
+  my $l1;
+  eval { $l1= $p->parsefile($fnm, ErrorContext => 3) }; 
+  if ($@)
+  {
+    print "parsefile failed fnm=[$fnm]:\n", $@, "\n";
+    return undef;
+  }
+
   # print "l1: ", Dumper ($l1);
+  # my $s_l1= Util::XML_Parser_Tree::to_string (@$l1); print "s_l1=[$s_l1]\n";
   my ($tag, $nc, @rest)= @$l1;
   # print "res: ", Dumper ($res);
 
@@ -145,9 +209,18 @@ sub parse
            if ($t1 eq '0') {} # skip text
         elsif ($t1 eq 'tag')
         {
-          # print "t2: [$t2]\n";
-          push (@{$note->{'tags'}}, $t2->[2]);
-# ZZZ
+          my $tag= $t2->[2];
+          push (@{$note->{'tags'}}, $tag);
+
+          if ($tag eq 'system:template')
+          {
+            $note->{'is_template'}= 1;
+          }
+          elsif ($tag =~ m#system:notebook:(.+)#)
+          {
+            $note->{'notebook'}= $1;
+          }
+          # TODO: maybe there other tags as well...
         }
       }
     }
@@ -164,7 +237,186 @@ sub parse
   $note;
 }
 
-1;
+=head1 Group 1+2: glue
+
+=cut
+
+sub text_to_lines
+{
+  my $note= shift;
+
+  my $x= $note->{'text'};
+
+  # print "x: ", Dumper($x);
+
+  my $nc= $x->[2];
+  # print "nc: ", Dumper($nc);
+  shift (@$nc); # remove the text-element's attributes
+  my $s= Util::XML_Parser_Tree::to_string (@$nc);
+
+  # split drops the new lines at the end, so we need to go the extra mile
+  my $cnt= length ($1) if ($s=~ s#(\n+)$##);
+  my @s= split ("\n", $s);
+  for (my $i= 1; $i < $cnt; $i++) { push (@s, '') }
+
+  # print "complete string: [$s]\n";
+  # print "s: ", Dumper (\@s);
+  # print "cnt: ", $cnt, "\n";
+
+  my $title= $s[0];
+  # TODO: compare existing title
+  $note->{'title'}= $title unless ($note->{'title'});
+  $note->{'lines'}= \@s;
+
+  # ($title, @s);
+  1;
+}
+
+sub parse_lines
+{
+  my $note= shift;
+
+  # print "text: ", Dumper ($note->{'text'});
+  my @lines= @{$note->{'lines'}};
+  my $start= join ('', $s_text, $s_note_content, shift (@lines));
+  my $x= parse_string (join ("\n", $start, @lines, join ('', $e_note_content, $e_text)));
+  $note->{'text'}= $x->[1];
+}
+
+sub parse_string
+{
+  my $str= shift;
+
+  # print "str=[$str]\n";
+  my $p= new XML::Parser (Style => 'Tree');
+  # print "p: ", Dumper ($p);
+  my $l1;
+  eval { $l1= $p->parsestring($str, ErrorContext => 3) }; 
+  if ($@)
+  {
+    print "parsestring failed str=[$str]:\n", $@, "\n";
+    return undef;
+  }
+  # print "l1: ", Dumper ($l1);
+  $l1;
+}
+
+=head1 Group 2: text generator
+
+=cut
+
+sub add_lines
+{
+  my $note= shift;
+
+  foreach my $line (@_)
+  {
+    my @lines= split (/\n/, $line);
+    @lines= ('') unless (@lines);
+    # print "line=[$line] lines: ", main::Dumper (\@lines);
+    push (@{$note->{'lines'}}, @lines);
+  }
+
+  $note->{'e_updated'}= time();
+}
+
+sub save
+{
+  my $note= shift;
+  my $out_dir= shift;
+  my $fnm_out= shift;
+
+  my ($title, $uuid, $ts_updated, $ts_md_updated, $ts_created, $e_updated, $lines, $is_template, $nb_name)=
+    map { $note->{$_} } qw(title uuid last-change-date last-metadata-change-date create-date e_updated lines is_template notebook);
+
+  # sanitize data
+  $note->{'uuid'}= $uuid= Tomboy::get_uuid() unless ($uuid);
+  $note->{'title'}= $title= $uuid unless ($title);
+
+  if ($e_updated)
+  {
+    $note->{'last-metadata-change-date'}= $ts_md_updated=
+    $note->{'last-change-date'}= $ts_updated= Tomboy::ts_ISO($e_updated);
+  }
+
+  $note->{'create-date'}= $ts_created= $ts_updated unless ($ts_created);
+
+# print "tags: ", Dumper ($note->{'tags'});
+  my @tags= ();
+  push (@tags, 'system:template') if ($is_template);
+  push (@tags, 'system:notebook:'. $nb_name) if ($nb_name);
+
+  unless (defined ($fnm_out))
+  {
+    $fnm_out= $out_dir if ($out_dir);
+    $fnm_out.= $uuid . '.note';
+  }
+
+  unless (open (FO, '>:utf8', $fnm_out))
+  {
+    print STDERR "can't write to [$fnm_out]\n";
+    return undef;
+  }
+
+  print "writing note [$fnm_out]: [$title]\n"; # TODO: if verbose
+
+  print FO chr(65279); # write a BOM, Tomboy seems to like that
+  print FO <<EOX;
+<?xml version="1.0" encoding="utf-8"?>
+<note version="0.3" xmlns:link="http://beatniksoftware.com/tomboy/link" xmlns:size="http://beatniksoftware.com/tomboy/size" xmlns="http://beatniksoftware.com/tomboy">
+EOX
+  print FO '  <title>'. Util::XML_Parser_Tree::tlt($title) ."</title>\n";
+  print FO '  ', $s_text, $s_note_content;
+
+  foreach my $line (@$lines)
+  {
+    print FO $line, "\n";
+  }
+
+  print FO $e_note_content, $e_text, "\n";
+
+  foreach my $f (@fields_seq1)
+  {
+    print_attribute (*FO, $note, $f);
+  }
+
+  if (@tags)
+  {
+    print FO "  <tags>\n";
+    foreach my $tag (@tags) { print FO "    <tag>", $tag, "</tag>\n"; }
+    print FO "  </tags>\n";
+  }
+
+  foreach my $f (@fields_seq2)
+  {
+    print_attribute (*FO, $note, $f);
+  }
+
+  print FO "</note>"; # No newline at end of file, that's how Tomboy does that ...
+  close (FO);
+
+  $fnm_out;
+}
+
+sub print_attribute
+{
+  local *F= shift;
+  my $n= shift;
+  my $f= shift;
+
+    my $a= $n->{$f};
+    unless (defined ($a))
+    {
+      my $x= $fields{$f};
+      return if (exists ($x->{'supress'})); # supress the default for that one
+      my $b;
+      if (exists ($x->{'default'})) { $b= $x->{'default'} }
+      # TODO: elsif exists function ....
+      $n->{$f}= $a= $b if (defined ($b));
+    }
+
+  print F '  <', $f, '>', $a, '</', $f, ">\n";
+}
 
 __END__
 
@@ -174,7 +426,19 @@ __END__
 
 =head1 BUGS
 
-* XML::Parser throws exceptions, these are currently not handled.
+* XML::Parser throws exceptions, these are currently not handled well.
 
 =cut
 
+  <tags>
+    <tag>system:notebook:Kalender 2014</tag>
+  </tags>
+
+Template:
+  <tags>
+    <tag>system:template</tag>
+    <tag>system:notebook:Kalender 2014</tag>
+  </tags>
+
+1;
+
diff --git a/perl/Gnome-Tomboy/s1.pl b/perl/Gnome-Tomboy/s1.pl
index 8afca80..81c9bde 100755
--- a/perl/Gnome-Tomboy/s1.pl
+++ b/perl/Gnome-Tomboy/s1.pl
@@ -1,5 +1,15 @@
 #!/usr/bin/perl
 
+=head1 NAME
+
+  s1.pl
+
+=head1 DESCRIPTION
+
+Script to play with Tomboy::Note::Simple for testing.
+
+=cut
+
 use strict;
 
 use lib 'lib';
@@ -9,17 +19,49 @@ use Tomboy::Note::Simple;
 use Data::Dumper;
 $Data::Dumper::Indent= 1;
 
-my $note_fnm= shift (@ARGV);
+die "no note filename specified" unless (@ARGV);
+
+open (FO, '>:utf8', 'do_verify.sh');
+while (my $arg= shift (@ARGV))
+{
+  process_note ($arg);
+}
+close (FO);
+
+exit (0);
+
+sub process_note
+{
+  my $note_fnm= shift;
 
-die "no note filename specified" unless ($note_fnm);
+print '='x90, "\n";
+print "process_note note_fnm=[$note_fnm]\n";
 
 # V1:
-# my $n= parse Tomboy::Note::Simple ($note_fnm);
+my $n= parse Tomboy::Note::Simple ($note_fnm);
 
 # V2:
-my $n= new Tomboy::Note::Simple; $n->parse ($note_fnm);
+# my $n= new Tomboy::Note::Simple; $n->parse ($note_fnm);
 
-print "n: ", Dumper ($n);
+$n->text_to_lines ();
+# print "lines: ", Dumper ($n->{'lines'});
+$n->parse_lines ();
+# print "n: ", Dumper ($n);
+
+my $saved= $n->save();
+
+print "saved=[$saved]\n";
+my @cmd= ('diff', '-u', $note_fnm, $saved);
+print join (' ', @cmd), "\n";
+my $rc= system (@cmd);
+print "rc=[$rc]\n\n";
+
+if ($rc != 0)
+{
+  print FO join (' ', @cmd), "\n";
+}
+
+  $rc;
+}
 
-exit (0);
 
-- 
GitLab