Benutzer:Turelion/Kochrezept-Datenbank

Grundidee Bearbeiten

Erstellen

  1. einer Datenbank in verschiedenen DBMS,
  2. einer GUI für manuelle Ein- und Ausgabe in verschiedenen Programmiersprachen, und
  3. eines Programms, daß das WB-Kochbuch nach Rezepten durchparst und in der Datenbank speichert, ebenfalls in verschiedenen Programmiersprachen.

Auf diese Weise können mehrere bereits existierende Projekte miteinander verknüpft werden, z.B. Einführung in SQL, Perl-Programmierung: DBI, Perl-Programmierung: wxWidgets, das Kochbuch, und Alternativen(PostGreSQL/Oracle/..., PHP/C/C++/Python/Ruby/..., QT/GTK/TK/...).

Die entsprechenden Beiträge sollten in Extra-Artikeln gespeichert werden, entsprechend den WB-Namenskonventionen. Auf diese Weise können sie sowohl auf den Projektseiten, als auch in diesem übergreifenden Projekt eingebunden werden.

Kochbuch: verwaiste Rezepte => http://de.wikibooks.org/wiki/Kochbuch:_verwaiste_Rezepte

Kochbuch/_Bananenbrot

Einladungs-Textbaustein Bearbeiten

Hallo, liebe Autoren des Buchprojekts XXX,

auf Benutzer:Turelion/Kochrezept-Datenbank habe ich ein programmiersprachenübergreifendes, modulares Projekt angelegt. Es geht darum, daß der Leser 1. die Möglichkeit erhält, sich die Inhalte unserer Wikibooks-Rezeptsammlung in einer schicken Datenbank samt GUI-Bedienoberfläche auf dem heimischen Rechner zu installieren. Jeder Programmierer weiß, daß es verschiedene Ansätze gibt, diese Aufgabe zu leisten, z.B. durch die Auswahl der Programmiersprache oder der Datenbank, jede Programmiersprache bietet auch noch verschiedene GUI-Systeme an (QT/GTK/wxWidgets/etc), und deshalb möchte ich dem Leser eine Auswahl dieser verschiedenen Ansätze anbieten.

Ich habe das Projekt bisher in Perl mit wxWidgets und PostGreSQL realisiert. Ich möchte euch gerne dazu einladen, euch daran zu beteiligen, und ein Pendant in eurer Programmiersprache/Datenbank/GUI zu entwickeln. Ich hoffe, daß davon auch euer Buchprojekt profitiert, ich verspreche mir, daß Ihr und Eure Kollegen von den Schwester-Buchprojekten die Gelegenheit erhaltet, über den eigenen Tellerrand hinüberzuschauen, und Kontakte mit anderen Autoren zu knüpfen, mit denen Ihr bisher nichts zu tun hattet. Außerdem hoffe ich, daß alle beteiligten Bücher einen erhöhten Wiedererkennungswert bei ihren Lesern erhalten, einfach wegen des "Kenne ich, dasselbe wie in C++/Python/Perl/PHP/sonstiges"-Effekts.

Auf gute Zusammenarbeit und Beste Grüße
Turelion

Datenstruktur Bearbeiten

Tabelle Rezepte: Rezeptnummer (als Primärschlüssel mit Autoinkrement), Bezeichnung, Kurzbeschreibung, Zubereitung, Tipps, Erfasser, Erfasst

Tabelle Rezeptzutaten: Rezeptzutatnummer (als Primärschlüssel mit Autoinkrement), Zutatnummer, Rezeptnummer, Menge

Tabelle Zutaten: Zutatnummer (als Primärschlüssel mit Autoinkrement), Einheit, Zutat

Problem: Wie kriegt man eine Beschreibung wie "3 reife Bananen" mengeneinheitsmäßig in den Griff?

DDL-Code Bearbeiten

Tabelle in PostGreSQL, muß natürlich noch an das Thema angepaßt werden:

CREATE TABLE songlist (
    tonart character varying(10),
    inhalt text,
    link character varying(50),
    titel character varying(100),
    songnr integer NOT NULL,
    loeschkz integer DEFAULT 0
);

GUI-Modul Bearbeiten

Hier mal ein kleines Progrämmchen für einen ähnlichen Zweck. Es verwendet zur Zeit Perl, wxWidgets und PostGreSQL.

#!/usr/bin/perl

use strict;
use warnings;

package Editor;
use Wx qw (wxVERTICAL wxHORIZONTAL wxTOP wxLEFT wxGROW wxTE_RIGHT wxTE_MULTILINE wxTE_LEFT);
use Wx::Event qw (EVT_BUTTON);
use base 'Wx::App';
use DBI;
use Encode;
my $titelfeld;
my $tonartfeld;
my $inhaltfeld;
my $panel;
(my $maxsongnr, my $minsongnr)=GetSongBreaks();
print "LOG MAIN: $maxsongnr, $minsongnr\n";

my $titel; my $tonart; my $inhalt;
my $songnr=1;

sub OnInit {
  if(!defined($songnr)) {$songnr=1;}
  my $app=shift;
  my $frame=Wx::Frame->new( undef, -1, 'Logosong-Editor', [-1, -1], [500, 400]);
  $panel = Wx::Panel->new( $frame, -1); 
  ($titel, $tonart, $inhalt)=readdata($songnr);
  #Problem: Wie übergebe ich die Ergebnisse der Datensatzabfrage an die GUI-Elemente?
  #Hinweis: Bei Aufbau einer Checkliste sollte unbedingt auch der Punkt "Laufende Nummer des Kontrollelements plausibel?" aufgenommen werden.
  $titelfeld = $panel->{'term1'} = Wx::TextCtrl->new ( $panel, -1, "$titel",  [-1,-1], [400,-1], wxTE_LEFT );
 
  $tonartfeld = $panel->{'term2'} = Wx::TextCtrl->new ( $panel, -2, "$tonart", [-1,20], [400,-1], wxTE_LEFT ); 

  #Frage: Wie bringe ich Felder dazu, UTF-8-spezifisch zu reagieren?
  #Antwort: Scheint nicht das Hauptproblem gewesen zu sein
  #Stattdessen neue Frage: Wie kriege ich die ListBox ans Laufen?
  # 1. Versuch: TextCtrl mit wxTE_LEFT. Problem: Einzeilige Ausgabe, trotz genügend Platz nach oben und unten.
  # 2. Versuch: ListBox mit wxLB_SINGLE, siehe http://www.nntp.perl.org/group/perl.wxperl.users/2007/06/msg2665.html
  # 3. Versuch: TextCtrl mit wxTE_MULTILINE!! Klappt!!
  $inhaltfeld = $panel->{'term3'} = Wx::TextCtrl->new ( $panel, -3, "$inhalt", [-1,60], [500,300], wxTE_MULTILINE ); 

  my $line=365;
  my $previousbutton = Wx::Button->new( $panel, -4, ' Zurück ', [-1, $line], [100, 30]);
     EVT_BUTTON($panel, $previousbutton, sub { $songnr--; ChangeMaskContent('-'); } ); 
  my $forwardbutton = Wx::Button->new( $panel, -5, ' Vor ', [101, $line], [50, 30]);
     EVT_BUTTON($panel, $forwardbutton, sub { $songnr++; ChangeMaskContent('+'); } ); 
  my $deletebutton = Wx::Button->new( $panel, -6, ' Löschen ', [200, $line], [100, 30]);
     EVT_BUTTON($panel, $deletebutton, sub {print "LOG: Delete: \n"; DeleteRSet(); } ); 
  my $savebutton = Wx::Button->new( $panel, -7, ' Speichern ', [301, $line], [100, 30]);
     EVT_BUTTON($panel, $savebutton, sub {print "LOG: Save: \n"; SaveRSet(); } ); 
  my $newbutton = Wx::Button->new( $panel, -8, ' Neu ', [451, $line], [50, 30]);
     EVT_BUTTON($panel, $newbutton, sub {print "LOG: New: \n"; EmptyContent(); } ); 

  $app->SetTopWindow($frame);
  $frame->Show(1);
}

sub ChangeMaskContent{
  #Problem: Wie kriege ich die GUI-Eigenschaften zu packen?
  #gewählte Lösung: Auslagerung der Variablendeklaration aus der OnInit-Funktion
  #Frage: Kann man das Panel auch ohne "shift" packen?
  #Antwort: Ja, siehe oben!!
  my @values=readdata($_[0], $_[1]);

  $titelfeld->SetValue($values[0]); 
  $tonartfeld->SetValue($values[1]); 
  Encode::_utf8_on($values[2]);
  #print "LOG h: $h\n";
  $inhaltfeld->SetValue($values[2]);
}

sub EmptyContent{
  $titelfeld->SetValue(''); 
  $tonartfeld->SetValue(''); 
  $inhaltfeld->SetValue('');
  $songnr=$maxsongnr+1;
}

sub SaveRSet {
  my $titel=$titelfeld->GetValue();
  my $tonart=$tonartfeld->GetValue();
  my $inhalt=$inhaltfeld->GetValue();
  print "LOG: $titel, $tonart, $inhalt, $songnr>$maxsongnr\n";
  my $query;
  if($songnr>$maxsongnr) {
    $query = "insert into songlist(titel, tonart, inhalt) values('$titel', '$tonart', '$inhalt')";  } 
  else {
    $query = "update songlist set titel='$titel', tonart='$tonart', inhalt='$inhalt' where songnr=$songnr";  }
  MyDo($query);
  ($maxsongnr, $minsongnr)=GetSongBreaks();
}

sub DeleteRSet {
  my $query = "update songlist set loeschkz=1 where songnr=$songnr";  
  MyDo($query);
  ChangeMaskContent('+');
  ($maxsongnr, $minsongnr)=GetSongBreaks();
}

sub GetSongBreaks {
  my $query = "select max(songnr), min(songnr) from songlist where not loeschkz=1";  
  my @row=MyPreExec($query);
  return @row;
}

sub readdata{
  my $sign=$_[0];
  if ($songnr>$maxsongnr) {$songnr=$maxsongnr;}
  if ($songnr<$minsongnr) {$songnr=$minsongnr;}
  print "LOG: $songnr";
  my $query = "select titel, tonart, inhalt from songlist where songnr=$songnr and not loeschkz=1";
  my @row=MyPreExec($query);
  if(!@row) {
    if($sign eq '-') {$songnr--; @row=readdata('-');} 
      else {$songnr++; @row=readdata('+');}
  }
  if(!defined($row[1])) {$row[1]=" ";}
  print ": $row[0]\n";
  return @row;
}

package main;

Editor->new->MainLoop;

Datenbankzugriffsmodul Bearbeiten

Diese beiden Funktionen könnten als Vorbild dienen:

sub MyDo {
  my $query = $_[0];
  require('login.pl');
  my ($db_name, $db_user, $db_pass) = loginfunc();
  my $dbc = DBI->connect("DBI:Pg:dbname=$db_name", "$db_user", "$db_pass");
  print "LOG: QUERY: $query\n";
  $dbc->do($query);
  $dbc->disconnect;
}

sub MyPreExec {
  my $query = $_[0];
  require('login.pl');
  my ($db_name, $db_user, $db_pass) = loginfunc();
  my $dbc = DBI->connect("DBI:Pg:dbname=$db_name", "$db_user", "$db_pass");
  my $datensatz=$dbc->prepare($query);
  $datensatz->execute();
  my @row=$datensatz->fetchrow_array();
  $datensatz->finish();
  $dbc->disconnect;
  return @row;
}

Überlegungen Bearbeiten

  1. Vielleicht kann man die beiden Funktionen zusammenführen.
  2. MyDo und MyPreExec könnten umbenannt werden zu DMLFunc und DQLFunc.
  3. Beide Funktionen könnten in die login.pl ausgelagert werden. Oder zumindest in eine andere Bibliothek. Was wäre ein geeigneter Name?

Kochbuch-Reader Bearbeiten

Problem: Wie kriegt man eine Beschreibung wie "3 reife Bananen" mengeneinheitsmäßig in den Griff?

Gibt es ein Perl-Modul zum Browsen und Auswerten des HTTP-Response? Bestimmt, nachschauen. Ansonsten dürfte wget reichen. Gibt es auch für Windows, wie ich jetzt herausgefunden habe.

 html2wiki:
 http://search.cpan.org/~diberri/HTML-WikiConverter-0.61/lib/HTML/WikiConverter.pm
 wiki2text
 http://search.cpan.org/~dprice/Text-MediawikiFormat-0.05/lib/Text/MediawikiFormat.pm


Dieses Skript zieht sich im ersten Schritt die alphabetische Rezeptliste, und merkt sich die Links zu den Einzelrezepten in einem Array. Im zweiten Schritt geht es die eben gewonnene Rezeptliste durch, zieht sich die jeweiligen Seiten und analysiert diese. Während das Skript entwickelt wird, wird dieser Vorgang nach dem ersten Durchgang abgebrochen, um Wikibooks zu schonen.

In diesem Skript besteht bestimmt noch Optimierungsbedarf.

#!/usr/bin/perl

use strict;
use warnings;

my $posa;
my $poso;
my $a;
my $endtime;
my $checktime=0;
my $ofile="Kochbuch:_Rezepte_alphabetisch";
my $wiki="wiki/";
my $cookbook="Kochbuch/";
my $overview="http://de.wikibooks.org/";
my $preflag=0;
my $beschreibungsflag=0;
my $zutatenflag=0;
my $zubereitungsflag=0;
my $anmerkungsflag=0;
my $erfasstflag=0;
my $beschreibung;
my @zutaten;
my $zubereitung;
my $anmerkung;
my $erfasser;
my $erfasst;
my @rezeptlinks=&fetchoverview;

#print "@rezeptlinks \n";
#print $starttime . "\n";
#exit;

foreach my $rezept (@rezeptlinks) {
  if(not -r "_$rezept")   {
    my $input=$overview . $wiki . $cookbook . "_" . $rezept;
    print "Fetching:$input\n";
    #exit;
    open(FETCH, "wget $input|");
    close(FETCH) || die "$input konnte nicht gedownloaded werden\n";
  }
  open(READ, "_$rezept") || die "Datei _$rezept konnte nicht geöffnet werden\n";
  while(<READ>) {
    $a=$_;
    chomp($a);
    if(index($a, "</pre>", 0)>-1 || index($a, "</ul>", 0)>-1) {
      $preflag=0;
      $beschreibungsflag=0;
      $zutatenflag=0;
      $zubereitungsflag=0;
      $anmerkungsflag=0;
      $erfasstflag=0;
    }
    if($preflag) {
      if($beschreibungsflag) {$beschreibung.=$a;}
      if($zutatenflag) {
        substr($a, 0, 4)="";
        substr($a, -5, 5)="";
        push(@zutaten, $a);
      }
      if($zubereitungsflag) {$zubereitung.=$a;}
      if($anmerkungsflag) {
        substr($a, 0, 4)="";
        substr($a, -5, 5)="";
        $anmerkung.=$a;
      }
      if($erfasstflag) {$erfasst.=$a;}
    }
    if(index($a, "<pre>", 0)>-1 || index($a, "<ul>", 0)>-1) {$preflag=1;}
    if(index($a, "<p><b>$rezept</b></p>", 0)>-1) {$beschreibungsflag=1;}
    if(index($a, "<p><a name=\"Zutaten\" id=\"Zutaten\"></a></p>", 0)>-1) {$zutatenflag=1;}
    if(index($a, "<p><a name=\"Zubereitung\" id=\"Zubereitung\"></a></p>", 0)>-1) {$zubereitungsflag=1;}
    if(index($a, "<p><a name=\"Tipps.2FAnmerkungen\" id=\"Tipps.2FAnmerkungen\"></a></p>", 0)>-1) {$anmerkungsflag=1;}
    if(index($a, "<p><i><b>Erfasst von: ", 0)>-1) {
      $erfasstflag=1;
      $erfasser=substr($a, index($a, ":", 0)+2, index($a, "</b></i></p>", 0)-index($a, ":", 0)-3);
    }
  }
  close(READ);  
  print "\n\n#####\n$beschreibung\n@zutaten\n$zubereitung\n$anmerkung\n$erfasst\n";

  #5 Sekunden Kunstpause, um Wikibooks nicht zu überlasten!!
  $endtime=time()+5;
  while($checktime<$endtime) {$checktime=time();}
  exit;
}

sub fetchoverview {
  my @output;
  if(not -r $ofile) {
    my $input=$overview . $wiki . $ofile;
    print "Fetching:$input\n";
    #exit;
    open(FETCH, "wget $input|");
    close(FETCH) || die "$input konnte nicht gedownloaded werden\n";
  }
  open(READ, "$ofile") || die "Datei $ofile konnte nicht geöffnet werden\n";
  while(<READ>) {
    $a=$_;
    $posa=index($a, "<a href=", 0);
    if($posa>-1){
      $poso=index($a, "title=", $posa);
      #$poso=index($a, "</a>", $posa);
      if(index($a, "/wiki/Kochbuch/", 0)>-1) {
        if($poso>$posa) {
          #print "$posa $poso ";
          $a=substr($a, $posa+25, $poso-$posa-27);
          push (@output, $a);
          print "$a\n";
	}
      }
    }
  }
  close(READ);
  return @output;
}