Archiv der Kategorie: Eigene Tabellen

Haushaltsbuch für Minimalisten

Ich lieeebe einfache Lösungen! Da ich umständehalber meine Haushaltsausgaben besser überwachen möchte, hab ich mir eine wirklich simple Lösung einfallen lassen. Man braucht dazu ein Spreadsheet und eine Datenbank, ich bin dazu Windows-rückfällig geworden und arbeite mit Excel und meinem heißgeliebten alten Access.

Kleiner Meckerpott zwischendurch:

ich fahre ja eigentlich auf meinem Hauptrechner sehr zufrieden mit Linux, aber was ich beim Versuch mit Base (dem LibreOffice Datenbankprogramm) meine Auswertungen zu fahren erlebt habe, war grottenmäßig. Ich hab stundenlang an einem Datenimport ASCII oder auch Excel/Calc hingeastet und nichts hat funktioniert. Keine Umlaute, kein Währungsformat, Zahl beharrlich als Text importiert und nachträglich nicht zu ändern,statt Daten zu importieren werden sie in Calc geöffnet… nee Leute, Base hat mich nicht überzeugt. Ich hab mein Spreadsheet gepackt (als xlsx gespeichert) und bin auf meinen alten Windows-Laptop umgezogen. Ah, ich sags euch, herrlich! Ein Paradies für eine alte Statistikerin wie mich!

Nachtrag: ich leiste Abbitte, es geht doch, und zwar nach dieser Anleitung:
https://help.libreoffice.org/latest/de/text/shared/guide/data_im_export.html
Ist zwar nicht ganz fehlerfrei, so werden Zahlen- und Datumsfelder nicht automatisch erkannt, und das Währungsformat fehlt gänzlich, das ist nicht schön. Es geht mit einem Umweg über Calc und die Zwischenablage, dann kann man mit einem Assistenten ähnlich wie in Access arbeiten und die zu importierenden Felder manuell auswählen. OK, wollen wir mal nicht so kleinlich sein und die €-Beträge als Number mit 2 Dezimalstellen führen. Für die Diagramme in Calc gibts dann wieder €.

Fangen wir vorne an: Die Datenerfassung

Aber ich schweife ab. Lasst uns ganz vorne abfangen. Die Grundlage jeder Statistik ist die Datenerfassung, da beißt die Maus kein Faden ab. Heißt in der Praxis: überall Kassenzettel mitgeben lassen und täglich eintippen. Manchen Läden bieten auch einen sog. e-Bon an (Kassenzettel elektronisch übermittelt) aber das hab ich noch nicht ausprobiert. Also reinhacken, und zwar in einer für die Auswertung sinnvollen Form. Dazu nimmt man gern eine Tabellenkalkulation wie Calc oder Excel, weil die angenehm zu bedienen sind. Meine Haushaltsbuch-Erfassungs-Tabelle sieht so aus:

flatfile

Das Flatfile

Ich höre schon den Aufschrei aller alten Datenbankler: das ist doch redundant und nicht normalisiert! Das, liebe Freunde der Statistik, ist ein klassisches Flatfile und hat mit Datenbankkonventionen  erstmal nix zu tun. da steht halt -zig mal edeka oder Nahkauf, und auch im Sortiment und Datum gibt es vielfache Einträge und basta. ShiSho (shit in shit out) sagten meine amerikanischen Kollegen damals, wenn du die richtigen Daten nicht reingibst, kommt auch nur Mist raus. Das Sortiment ist übrigens biologisch gewachsen, das habe ich mir beim Eintippen der Reihe nach einfallen lassen. Man sollte es recht konsequent verwenden und nicht ständig neue Sortimentseinträge erfinden. Zum Tippen gehts einfach, weil das Spreadsheet eine Auto-Vervollständigen-Funktion hat und es reicht wenn man nur die Anfangsbuchstaben eintippt. Das Datum gibt man pro Kassenzettel nur einmal ein und ziehts dann mit Copy and Paste runter.Beim Artikel sollte man ein wenig Disziplin walten lassen und die Namen für gleiche Artikel gleich schreiben. Andererseits ist eine Auswertung auf Artikelbasis schon ziemlich esoterisch, das reicht mir wenn ichs im Flatfile sehen kann.

Also, wir haben folgende Felder:

Artikel Preis Laden Datum Sortiment

Dann tippen wir brav unsere Kassenzettel ein. Wenn man das gleich jeden Tag erledigt, ist es in ein paar Minuten fertig. Und eins ist wichtig: ehrlich sein, nix unterschlagen, auch nicht das Eis am Sonntagnachmittag oder den Kaffee und Kuchen in der Konditorei.

Kleine Erklärung zwischendurch

Sie vermissen die „grossen“ Ausgaben wir Miete, Strom, Heizung, Telefon etc? Die sehe ich auf meinem Kontoauszug, und ich kann sie beim besten Willen nicht ändern. Natürlich kann man versuchen Strom zu sparen und statt die Heizung anzudrehen einen warmen Pullover anzuziehen, aber wir wollens mal nicht übertreiben. Ein wichtiger Posten allerdings fehlt bei mir, weil ich kein Fahrzeug habe: die Benzinkosten. Die treiben in vielen Haushalten die Kosten in die Höhe, und man sollte schon ein Auge darauf haben.

Erfassung fertig – und erste Schritte im Spreadsheet

Es lohnt sich schon nach etwa zwei Wochen, eine erste Auswertung zu fahren. Als erstes setzt man mal eine Summe unter die Spalte mit dem Preis, das sind logo die gesamten Ausgaben.

summepreis

Nützliches Werkzeug: der AutoFilter

Dann spielt man ein bisschen mit einem Auto-Filter und schaut sich mal an, wieviel man in einem bestimmten Laden ausgegeben hat, und zum Beispiel noch was man alles für einzelne Sortimente gekauft that. Leider ließ sich mein Screenshot-Utility nicht dazu überreden, das Autofilter-Fenster aufzunehmen, aber es ist eigentlich selbsterklärend. Man kriegt z.B. eine Liste alle vorhandenen Läden angezeigt, und kann einen oder mehrere auswählen. Ich nehme mal nur den  nahkauf: Und sehe sofort, dass ich da spätestens jeden zweiten Tag einkaufe.

nahkauf

Oder ich schau mir mal an, was ich so an Fleisch und Wurst eingekauft habe:

fleischwurst

Mhm, das ist recht viel. Man sieht hier auch rote Zahlen, die werden vom EKPreis abgezogen, das ist der sogenannte Frischerabatt wenn das MHD naht und die Ware heruntergesetzt ist. Ich schlag da ganz gern zu, das ist ja noch einwandfrei, man muss es nur zeitnah verbrauchen.

Seht ihr wie der Hase läuft? Mit dem Flatfile im Spreadsheet kann man sich schon ein bisschen mit den Daten vetraut machen, ehe es ernst wird und die Datenbank zum Einsatz kommt. Nicht verbasteln, die grossen Auswertungen mit Summen und Gruppierungen machen wir in der Datenbank, da gehts viel einfacher als im Speadsheet! Finde ich jedenfalls.

Auf gehts zum Datenbanken

Wenn man mit Calc gearbeitet hat, speichert man die Datei als xlsx ab, mit Excel erübrigt sich das natürlich. Dann, täterätää, gehts los: Access aufmachen und eine neue leere Datenbank anlegen.

(Anmerkung am Rande: wenn jemand herausfindet, wie man das Flatfile korrekt nach Base importieren kann, bitte Bescheid sagen! Ich habs aufgegeben und bleib bei Access.)

Zuerst müssen wir unser Flatfile laden. Das geht folgendermassen:

Unter externe Daten auf das Excel-Icon klicken, mit durchsuchen zum Excel-File navigieren, dden ersten Punkt „Importieren in eine neue Tabelle“ auswählen. Dann wird ein Auszug des ersten Arbeitsblattes angezeigt. Der Excel-Import in Access importiert immer nur ein ausgewähltes Arbeitsblatt, das ist eigentlich logisch, weil ja nur in eine einzelne Tabelle importiert wird. Man vergewissere sich, dass das richtige Arbeitsblatt angezeigt wird, und klickt auf Weiter- Erste Zeile enthält Spaltennamen auswählen, kurz drüberschauen ob alles paßt, weiter. Jetzt kommt ein eminent wichtiger Schritt, nämlich die Vergabe der richtigen Datentypen für die einzelnen Felder. Es ist eigentlich selbsterklärend:

Artikel Text, Preis Währung, Laden Text; Datum Datum/Uhrzeit, Sortiment Text. Und Schuss 🙂

Primärschlüssel soll von Access hinzugefügt werden, Weiter, Tabellenname vergeben, ich wähle „Rohdaten“, Fertig stellen. Wer möchte kann sich die Importschritte für die Zukunft speichern.

Ich öffne die Tabelle, und Halleluja, es kann losgehen!

tabelleinacc

Die erste Abfrage: wofür gebe ich mein Haushaltsgeld eigentlich aus?

Dafür werten wir den Preis pro Sortiment aus, da kommt dann raus was ich für Fleisch, Molkereiprodukte, Porto usw. tatsächlich ausgegeben habe. Ich mach sowas gern im Assistenten, die ganz harten Freaks tippen das SQL ein. Aber schön der Reihe nach, damit auch jeder mitkommt.

Das ist jetzt echt Access für blutige Anfänger, aber einmal exerzieren wir es durch, damit sich auch Leute zurechtfinden die noch nicht viel mit Access und Datenbanken überhaupt gearbeitet haben. Wir rufen den Abfrage-Assistenten auf, der steckt unter Erstellen/Abfrage-Assistent Icon, und wählen Auswahlabfrage-Assistent.  Als zugrundeliegende Tabellen müsste eigentlich unsere Rohdaten drinstehen, wir haben ja nur die eine Tabelle. Wir wählen die Felder Preis und Sortiment, und weiter. Detail sollte angewählt sein, weiter. Abfrageentwurf anwählen, fertig stellen. Das sieht so aus:

auswahleditor

Unten sind unsere gewählten Felder eingeblendet. Eminent wichtig ist oben in der Menüleiste das Summenzeichen, damit blenden wir nämlich die Aggregatfunktionen ein, die wir für diese Auswertung brauchen. Der Preis wird summiert und absteigend sortiert, das Sortiment wird gruppiert, so soll es aussehen:

summegruppierung

Wenn wir jetzt die Abfrage ausführen, erhalten wir folgendes prächtige Ergebnis:

abfrage_ergebnis

Na bitte, juckt es sie da nicht in allen Excel-oder Calc-Fingern, das schreit doch nach einem Diagramm-Törtchen! Calc möchte übrigens die Daten genau andersherum, also in der ersten Spalte die Beschriftung, in der zweiten Spalte die Summe von Preis. Sonst wirds kompliziert…

Ich hab wieder Linux zum Zug kommen lassen und machs mit Calc.

preisprosortimenttorte

Na, das war doch jetzt gar nicht schlimm, oder? Für Perfektionisten liefere ich noch das SQL der Abfrage nach:

SELECT Sum(Rohdaten.[Preis]) AS SummevonPreis, Rohdaten.[Sortiment]
FROM Rohdaten
GROUP BY Rohdaten.[Sortiment]
ORDER BY Sum(Rohdaten.[Preis]) DESC;

Weitere interessante Abfragen

Ich machs mir leicht und zeige hier nur das SQL, im Assistenten kanns sichs jeder selber zusammenpfriemeln. Da wäre als nächstes interessant, wieviel Geld ich in welchen Laden getragen habe, also die Summe über den Preis gruppiert nach Laden… hach ich liebe SQL, das ist doch sehr gut verständlich und einleuchtend:

SELECT Sum(Rohdaten.[Preis]) AS SummevonPreis, Rohdaten.[Laden]
FROM Rohdaten
GROUP BY Rohdaten.[Laden]
ORDER BY Sum(Rohdaten.[Preis]) DESC;

preis_pro_laden

Als letztes machen wir noch die Ausgaben pro Tag, für die ist ein Balkendiagramm sinnvoll. Erst das SQL:

SELECT Sum(Rohdaten.Preis) AS SummevonPreis, Rohdaten.Datum
FROM Rohdaten
GROUP BY Rohdaten.Datum
ORDER BY Rohdaten.Datum;

pro_tag_balken

Na, das ist doch mal aussagekräftig… jetzt kann man hingehen und in den Rohdaten nachschauen, was bei den Spitzenwerten an einem bestimmten Tag ausgegeben wurde, aber damit lasse ich euch jetzt alleine, da kann jeder selber spielen. Mir jedenfalls hat es Spaß gemacht, und ich liebe es, mein Haushaltsbuch auszuwerten. Da macht sogar das Kassenzettel erfassen Spaß!

Es geht voran mit dem Stichwortregister-Plugin: aus drei mach zwei

Ich hab mir heute nochmal die Erstellung von Admin-Menüs in WordPress zu Gemüte geführt, und beschlossen aus den drei Plugins zur Erstellung eines Stichwortregisters doch zwei zu machen.  Es sind ja auch getrennte Funktionalitäten, einmal wird die Stichwortliste als CSV aus der wp_posts erstellt, eine Ausgabeseite angelegt und in die wp_options eingetragen, und der Shortcode erzeugt, und zum zweiten wird die Negativliste gepflegt. Ausserdem bräuchte ich dann für die Negativliste eine Menütiefe von 3, und da wirds tricky, das kann WordPress nicht so ohne weiteres.

Man könnte die CSV-Datei, die Ausgabeseite und die Tabelle für die Negativliste auch bei Aktivierung des entsprechenden Plugins automatisch erzeugen, aber irgendwie ist es mir sympathischer, das manuell anzustossen. Muss man halt eine recht ausführliche Anleitung mitliefern, aber das krieg ich auch noch gebacken 😉

Jetzt erst mal weiter mit dem Plugin-Zusammenfassen. Läuft ganz gut, ich hab wohl recht sauber programmiert, Morgen mehr!

Update: Das war ja einfach. Ich habe ein schönes Tutorial bei Honar Systems für die Erstellung von WordPress Admin Pages mit Sub Pages gefunden, damit war die Hauptarbeit schon erledigt. Es gibt jetzt einen Menüeintrag „Stichwortregister“ mit zwei Untermenüs „Konfiguration“ und „CSV Datei“. Es gibt des weiteren einen eigenen Menüpunkt „Negativliste bearbeiten“. Hier mal nur ein kurzes Snippet für die Erzeugung der Menüs:

 function stichwortregister_admin_menu() {
add_menu_page(
__( 'Stichwortregister', 'de_DE' ),
__( 'Stichwortregister', 'de_DE' ),
'manage_options',
'stichwort-page',
'stichwortregister_admin_page_contents',
'dashicons-plugins-checked',
3
);
add_submenu_page( 'stichwort-page',
__( 'Stichwortregister', 'de_DE' ),
__( 'CSV-Datei', 'de_DE' ),
'manage_options',
'stichwort-page-sub-menu-csv',
'stichwortregister_sub_menu_admin_page_csv_contents');

add_submenu_page( 'stichwort-page',
__( 'Stichwortregister', 'de_DE' ),
__( 'Konfiguration', 'de_DE' ),
'manage_options',
'stichwort-page-sub-menu',
'stichwortregister_sub_menu_admin_page_contents');


}
add_action( 'admin_menu', 'stichwortregister_admin_menu' );

Die Funktionalitäten haben sich nicht geändert, ich hab sie nur ein bisschen anders einsortiert. Lief problemlos, ich hab da echt sauber gearbeitet. So, jetzt muss es aber gut sein. Zwei Plugins für ein Stichwortregister, damit leben wir einfach 😉

Perfektionisten würden jetzt noch die Stichwortliste in eine MySQL Tabelle packen, aber mir gefällt das mit dem CSV eigentlich ganz gut, vielleicht will man die Liste auch mal extern bearbeiten oder ergänzen. Ich lass es jetzt mal so.

Nachtrag: die Negativliste im praktischen Einsatz

Ich war noch die praktische Anwendung der Negativliste für das Stichwortverzeichnis schuldig, die liefere ich jetzt nach. Wir haben ja unsere Negativ-Wörter in der Tabelle negativliste stehen und können sie per Plugin pflegen.

Jetzt kommt ihr Einsatz: ich mach das an der Stelle, wo die Stichwortliste aus der CSV-Datei eingelesen und in ein Array weggeschrieben wird. Also, wir gehen in die Function csv_einlesen(). Und zwar an die Stelle, wo die CSV mit einer While not EOF-Schleife eingelesen wird. In jeder eingelesenen Zeile wird der Flag für die Negativliste erstmal auf 0 gesetzt. Dann geht man mit einem Select in die Tabelle negativliste und prüft, ob das aktuelle Wort enthalten ist, wenn ja wird der Flag auf 1 gesetzt. Dann geht man hin und schreibt das aktuelle Wort nur in das Array, wenn der Flag == 0 ist. Das wars!

$negativ_flag = 0;

//Solange nicht EOF erreicht, 1000 ist die maximale Zeilenlänge
while (($csv_array = fgetcsv ($handle, 1000)) !== FALSE ) {

foreach ($csv_array as $index) {

$negativ_flag = 0;
//mit Negativliste abgleichen
$db_item = $wpdb->get_results(
"SELECT * FROM negativliste WHERE wort LIKE '$index'");

if (count($db_item) > 0){
$negativ_flag=1;
}

//Nur ausgeben wenn das Wort nicht in der Negativliste enthalten ist
if ($negativ_flag == 0){
//Hier kommt der Knackpunkt: Neues Stichwort in Array schreiben
//***********************************
array_push($aktListe, $index);
//***********************************

} //Ende von if negativ_flag == false
}
}

Ich muss sagen, ich bin mit der Funktionalität sehr zufrieden, so kriegt man mit relativ wenig Aufwand doch recht saubere Stichwörter und kann die kleinen Füllwörtchen prima ausblenden. Mein Progrämmchen ist lernfähig, das grenzt schon ein bisschen an KI 🙂

Stichwortregister revisited: aus drei mach eins

Jetzt hab ich drei Plugins, um ein Stichwortregister zu erzeugen, das ist irgendwie nicht so zufriedenstellend, das könnte man sicher auch zusammenfassen.  Ich mach mal ein Brainstorming und schau was dabei rauskommt.

  • Bei Aktivierung des Plugins sollen ein paar Aufgaben erledigt werden: die Tabelle negativliste mit einigen (wenigen) Beispieldaten soll angelegt werden, einige Setup-Informationen sollen in die wp_options geschrieben werden. Das geht wahrscheinlich mit dem
    register_activation_hook( __FILE__, array( $this, 'example_activate' ) );
    
  • die Erstellung der CSV-Datei mit den Stichworten (aus dem Feld post_title erzeugt) soll manuell angestossen werden, damit man hier bessere Kontrolle hat.
  • Der Abgleich mit der Negativliste soll live bei Erstellung des Stichwortregisters erfolgen.
  • Fleißaufgabe: statt der CSV-Datei könnte man auch eine Custom Tabelle hernehmen, aber dabei ist mir der Webserver zu oft abgeraucht, da hab ich keine Lust drauf.

Das wird natürlich ein ewig langer Rattenschwanz, wenn man das alles in ein einziges Plugin packt, da müsste ich gucken ob man den Ablauf nicht besser straffen und verschlanken kann. Mal schauen ob mich die Arbeitswut noch packt… ich zweifle, die Rohfassung läuft ja ganz hübsch, das langt mir immer 😉

Update am Folgetag: Ich versuch mal meinen inneren Schweinehund zu überwinden und die drei Plugins doch zu einem Paket zusammenzufassen. Ich hab mir dazu mal eine lokale Kopie meines Praxis Dr. Inselfisch Blogs angelegt, auf der ich auf einer „leeren Wiesn“ entwickeln kann. Mal sehen wie weit ich komme.

Update drei Tage später: ich prokrastiniere. Mir ist das einfach zu fad, bereits funktionierende Code Snippets nochmal komplett neu zusammen zu fassen. Na, ich muss mal schauen. wahrscheinlich geh ich eh nochmal drüber, wenn ich das Stichwortverzeichnis auf einem Life-Blog einsetzen möchte. Erster Kandidat ist dieser hier 😉

Der Tabelleneditor für die Negativliste: die Screenshots

Die Negativliste bearbeiten geht nur auf der Admin-Seite, das hab ich mal so festgelegt. Ich habe einen neuen Admin Menüpunkt “ Negativliste bearbeiten“ angelegt, wenn man den aufruft sieht die Startseite so aus:

Negativliste-bearbeiten

Die bereits vorhandenen Worte werden tabellarisch angezeigt, Man kann sie Löschen, dann öffnet sich das Unterformular und man muss nochmal auf den Button „Löschen“ klicken:

Wort-loeschen

Erst dann wird der Delete aktiv:

 Wort-loeschen-OK

 

…oder man kann einen neuen Eintrag anlegen.

Wort-eintragen

Ein kleines, aber rundes Feature, finden sie nicht auch? 🙂

Was mich allerdings beim Entwickeln ein bisschen ausgebremst hat: die Routinen für den Insert und Create und Delete haben mir ein paar Mal den Webserver abgeschossen bis sie fehlerfrei durchgelaufen sind, da half nur ein kompletter Neustart von Apache, MySQL und Xampp. Das darf natürlich in einer „richtigen“ WordPress-Umgebung nicht passieren, da muss man noch Fehlerbehandlungsroutinen einbauen. ich hab da jetzt aber keine Lust mehr dazu.

So, was fehlt noch? Ach ja, die Negativliste wartet noch auf ihren Einsatz beim Aufbau des Stichwortregisters. Das überleg ich mir morgen, wo man da am Geschicktesten einhakt.

Stichwortverzeichnis Negativliste: wir basteln uns einen Tabelleneditor

Ich bin mir ziemlich sicher dass ich sowas in der Art schon mal gemacht habe, aber ich finde es nicht mehr. Na ja, das übt und ist eine hübsche kleine Fingerübung. Ich mach mal einen kurzen PAP:

  • Die Tabelle für die Negativliste wird beim Setup des Stichwort-Plugins mit angelegt und mit einigen wenigen Beispieleinträgen befüllt. Jedenfalls gehe ich davon aus, dass sie schon existiert.
  • Es gibt einen neuen Admin-Menüpunkt Negativliste bearbeiten. Hier wird die existierende Liste tabellarisch angezeigt.
  • Es gibt bei jedem Datensatz einen Button „Löschen“ (mit Rückfrage)
  • Es gibt unterhalb der Tabelle einen Button „Neues Wort eintragen“
  • Die Buttons machen jeweils eine Unterseite auf, auf der die gewählte Aktion nochmal bestätigt wird.

Das wars eigentlich schon. Dann wollen wir mal. Zuerst kommen die Einträge für das Admin-Menü:

add_action('admin_menu', 'negativliste_plugin_setup_menu');

function negativliste_plugin_setup_menu(){
add_menu_page( 'Negativliste', 'Negativliste bearbeiten', 'manage_options', 'negativliste', 'negativliste_init' );
add_submenu_page('Negativliste', 'Eintragen', 'Eintragen', 'manage_options', 'eintragen', 'eintragen_function');
add_submenu_page('Negativliste', 'Loeschen', 'Loeschen', 'manage_options', 'loeschen', 'loeschen_function');
}

Schauen wir uns als erstes mal die Funktion negativliste_init an. Zuerst wird gecheckt, ob die Tabelle negativliste schon vorhanden ist:

//Überprüfen ob Tabelle schon existiert
$table_exists = $wpdb->get_var( "SHOW TABLES LIKE 'negativliste'" ); 

if ($table_exists == "") {
echo "Tabelle negativliste existiert noch nicht";
... (hier kann die Tabelle neu erstellt werden)


Da muss ich nochmal ran, eigentlich sollte die Tabelle beim Initialisieren des Plugins Stichworttabelle mit angelegt werden. Hier nur mal Interessehalber der Code zum Erstellen:

//Begin function tabelle_erzeugen - legt die Tabelle negativliste mit einem Feld "wort" an
function tabelle_erzeugen(){

global $wpdb;

$charset_collate = $wpdb->get_charset_collate();

$sql = "CREATE TABLE negativliste (
wort text
) $charset_collate;";

require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );

} //End function tabelle_erzeugen

Interessant wirds im Else-Zweig der Funktion, da  kommt die Datenbankabfrage und der Aufbau der Tabelle zur Anzeige. Ich schau erst mal nach, ob die Tabelle überhaupt Datensätze hat und baue die Tabelle nur auf, wenn es mehr als 0 Datensätze sind:

function tabelle_bearbeiten(){

global $wpdb;
//Datensätze zählen & Ausgabe Anzahl
$count_query = "select count(*) from negativliste";
$num = $wpdb->get_var($count_query);
echo $num."&nbsp Einträge gefunden</br>";

if ($count_query <> 0) {

$anzeige_query = $wpdb->get_results("SELECT wort FROM negativliste ORDER BY wort");
...

So wird der Tabellenhead zusammengebaut:

echo "<table border='1' cellpadding='5'>
<tr>
<th>Wort</th>
<th>delete</th>
</tr>";

Danach steppe ich durch alle gefundenen Datensätze durch und baue ein Formular mit einem Löschen-Button zusammen. Der Button ruft die Unterseite loeschen auf und gibt als Parameter das aktuelle Wort mit.

echo "<form method='post'>";
foreach ($anzeige_query as $dsatz) {
echo "<tr>";
$id = $dsatz->wort;

echo "<td>" . $dsatz->wort . "</td>";
echo "</td>" .
"<td><input type='submit' name='löschen' formaction='admin.php?page=loeschen&aktuell=".$id."' value='löschen'></td>" .

"</tr>";
}// ende von foreach dsatz

Jetzt kommt nur noch ein Link zur der Seite auf der man einen neuen Datensatz eintragen kann, dann wars das auch schon:

echo "<p><input type='submit' name='eintragen' formaction='admin.php?page=eintragen' value='Neues Wort eintragen'></p></form> ";

Jetzt fehlen noch die Funktionen zum Eintragen eines neuen Datensatzes und zum Löschen des gewählten Datensatzes. Zuerst die zum Eintragen:

Zuerst wird ein kleines Formular aufgebaut: es gibt ein Textfeld, in das man das neue Wort eintragen kann, und einen Button zum Speichern. Wenn auf diesen Button geklickt wird, wird der Insert aufgerufen und kriegt das Wort als Parameter mit.

if (isset($_POST['neuesWortEintragen'])){
			$aktWort = $_POST["wort"];
			echo "Neues Wort ".$aktWort." wird eingetragen";
			
	// Insert-Anweisung erstellen
    $check = $wpdb->insert( 'negativliste', array( 'wort' => ''.$aktWort.'' ) );

    if($check) {
        echo "Ein neuer Datensatz erfolgreich hinzugefügt";
    }
	} // ende von isset 

Dann kommt noch ein Link zurück zum Hauptformular, und das wars.

 

echo "</form>";

echo "<a href='admin.php?page=negativliste'>Zurück zur Übersicht</a>";

Das Formular zum Löschen eines Datensatzes sieht ganz ähnlich aus, das Textfeld ist allerdings schreibgeschützt, es dient nur zur Anzeige des als Parameter übergebenen Worts.

function loeschen_function(){

global $wpdb;

$aktWort = $_GET['aktuell'];
echo $aktWort." wird gelöscht";

echo "<h2><Löschen</h2>";

echo "<form action='admin.php?page=loeschen&aktuell='".$aktWort."' method='post'>";
echo "<p><input name='wort' type='text' value = '$aktWort' readonly></p>";
echo "<p><input type='submit' name='WortLoeschen' value='Wort löschen'></p>";
...

Wenn auf den Button „Wort Löschen“ geklickt wird, triggert der Delete-Befehl:

if (isset($_POST['WortLoeschen'])){
$aktWort = $_POST["wort"];
echo "Wort ".$aktWort." wird gelöscht</br>";

// Delete-Anweisung erstellen
$check=$wpdb->query("DELETE FROM negativliste WHERE wort = '$aktWort'");

if($check) {
echo "Ein Datensatz erfolgreich gelöscht";
}
echo "</form>";

Auch hier kommt noch der Link zurück zur Übersicht, und das wars.  Jetzt fehlen noch ein paar Screenshots, aber dafür gibts einen neuen Beitrag.

Stichwortverzeichnis: die Negativ-Liste

Was soll die können? Nun, ganz einfach. Man soll da Wörter eintragen können, die in dieser Schreibweise nicht im Stichwortregister auftauchen sollen.

Hintergrund: die Stichwortliste wird ja programmgesteuert erzeugt und nimmt prinzipiell alle Wörter auf, die großgeschrieben sind. Die werden dann noch von etlichen Sonderzeichen bereinigt, aber es kann natürlich nicht geprüft werden, ob es sich wirklich um Substantive handelt oder ob nur ein Wort am Satzanfang großgeschrieben wurde.  Da rutschen dann schon mal Wörter durch wie Es, Da, Was, Das, So… klar was ich meine? Sind halt keine sinnvollen Stichwörter. Ich hab in der Access-Version mal versuchsweise alle Wörter ausgeblendet, die weniger als 3 Buchstaben haben, aber so ganz das Gelbe vom Ei ist das auch nicht. Auf dem Programmierblog hier würde dann z.B. alle rausfallen was SQL oder PHP oder AI  heißt – nicht gut!

Woher soll mein Plugin aber wissen, welche Wörter es ausblenden soll? Eben! Hier kommt die Negativliste ins Spiel. Ich versuche es mal mit einer Tabelle, obwohl ein CSV sicher auch sinnvoll wäre, das könnte man auch extern bearbeiten. Wie auch immer, es muss ein Admin-Menüpunkt her „Negativliste bearbeiten“. Und die Liste muss an geeigneter Stelle überprüft werden, das mach ich am besten an der Stelle, wo das CSV mit den Stichwörtern in ein Array eingelesen wird. Wenn ein Wort in der Negativliste gefunden wird, soll es nicht ins Array aufgenommen werden. Sollte so oder ähnlich funken, ich fang mal an und berichte später.

Darf ich vorstellen: Stichwort Plugin Teil 1, CSV-Datei erzeugen

Da ich die ganze Mechanik schon mal programmiert habe, in Access mit Visual Basic, hab ich mir relativ leicht getan es auch in PHP zu lösen. Was gar nicht schön war: bei komplexeren Datenbankoperationen ist mir x-mal der Webserver abgeraucht. Deswegen hab ich dann die Notbremse gezogen und bin auf eine CSV-Datei ausgewichen. Nicht die schlechteste Lösung, was Stabilität und Performance angeht.

Was hab ich gemacht? Ich geh da mal im Schnelldurchlauf durch, haben wir alles schon mal so oder in ähnlicher Form gehabt. Trotzdem, das eine oder andere ist vielleicht gut zu wissen.

Also,, zunächst wird ein Plugin mit einem Eintrag für das Admin-Menü erstellt. Dazu legt man eine Datei an, die folgendermassen aussieht:

/*
Plugin Name: Stichworttabelle
Description: Erzeugt eine neue Stichworttabelle aus den Titeln der Beiträge (post_title)
Author: Evi Leu
Version: 0.1
*/
    add_action('admin_menu', 'stichworttabelle_plugin_setup_menu');
	
	function stichworttabelle_plugin_setup_menu(){
    add_menu_page( 'Stichworttabelle', 'Stichworttabelle Plugin', 'manage_options', 'stichworttabelle', 'stichworttabelle_init' );
}
 
function stichworttabelle_init(){... HIER GEHTS MIT DER MAIN FUNCTION LOS

Diese Datei kommt in ein eigenes Unterverzeichnis „Stichworttabelle“ im Plugin-Verzeichnis deiner WP-Installation und heißt Stichworttabelle.php. Sie kann jetzt in der Liste der installierten Plugins aktiviert werden. Sie tut zunächst mal nichts ausser einen Eintrag im Admin-Menü zu erzeugen, der heißt „Stichworttabelle Plugin“ und ist erstmal noch eine leere Seite.

Die Menüseite wird in der Funktion function stichworttabelle_init() mit Leben gefüllt, erst kommt ein bisschen Infotex, dann wird der Name der Datenbank ermittelt und ausgegeben:

echo "<h1>Stichworttabelle neu erstellen</h1>";
	echo "Das Plugin hat zwei Funktionalitäten: </br></br>
	1. Diesen Admin-Menüpunkt Stichworttabelle Plugin, in dem die Stichworttabelle neu aufgebaut werden kann.</br>
	Aus Performancegründe und wegen der Runtime-Stabilität wurde die Stichwortbasis in eine externe CSV-Datei ausgelagert. Diese wird neu erstellt, falls sie nicht schon vorhanden ist. Falls sie schon vorhanden sein sollte, wird sie überschrieben. Man kann die Datei beliebig oft neu erzeugen, z.B. wenn es grössere Mengen neuer Beiträge gibt.</br></br>
	
	2. einen Shortcode [stichwortverzeichnis]. der an beliebiger Stelle in einem Beitrag oder einer Seite eingesetzt werden kann und dort ein Stichwortverzeichnis erzeugt.</br></br>";
	global $wpdb;
	
	//Datenbankname ermitteln
	$mydatabase=$wpdb->dbname;
	echo "Sie arbeiten auf der Datenbank: ".$mydatabase."</br>";
	echo "Stichworte werden aus den Titeln ihrer Beiträge erzeugt</br>";

Dann kommt ein kleines Formular, das aus genau einem Button besteht:

//***************Begin Formular
//Formular mit Button
//"
// Stichwortliste Datei neu erzeugen startet die array-Erzeugung fuellen und befüllt die Datei stichwortliste.csv wieder

echo "<form action = '#' method = 'post'>";
	
	echo "<input type='submit' id='el_button2' name='ButtonFuellen' value='Stichwortliste Datei neu erzeugen'>";
	echo "</form>";
	
	if (isset($_POST['ButtonFuellen'])){
			
			return tabelle_fuellen();
		}
//*****************End Formular

Wenn auf den Knopf gedrückt wird, wird die Funktion tabelle_fuellen aufgerufen. Jetzt wirds interessant:

function tabelle_fuellen(){
	
	$neuesArray=array_erzeugen();
	erzeuge_csv($neuesArray);
}

Die Variable $neuesArray wird mit Hilfe der Funktion array_erzeugen() befüllt. Diese erstellt eine Stichwortliste aus den Titeln aller Beiträge in der Tabelle wp_posts, dazu gleich mehr.

Dann wird die Funktion erzeuge_csv aufgerufen, sie kriegt als Parameter unser Array mit und schreibt die Einträge zeilenweise in eine Datei.

Frischauf, wir sehen uns die Funktion array_erzeugen() mal näher an. Der erste Teil mit den nötigen MySQL-Abfragen sieht so aus:

global $wpdb;

//Beginn Originalcode
$table_name = $wpdb->prefix.'posts';

	//Datensätze zählen & Ausgabe Anzahl
	$count_query = "select count(*) from $table_name where post_status='publish' and post_type = 'post'";
	$num = $wpdb->get_var($count_query);
	echo $num."&nbsp Beiträge gefunden</br>";
	
//******************

//Alle Datensätze vom Typ post und published ausgeben
$alleposts = $wpdb->get_results( "SELECT * FROM ".$table_name."
								where post_status='publish' and post_type = 'post' order by post_title");


Das übliche Spiel wenn man die veröffentlichten Beiträge ausgeben will, man braucht in der Where-Klausel die Bedingung post_status=’publish‘ and post_type = ‚post‘. Und zugegeben, man könnte statt select * auch select post_title verwenden, das fällt mir erst jetzt auf.

Jetzt stecken alle veröffentlichten Beiträge als Array in der Variablen $alleposts. Durch dieses Array steppe ich jetzt mit foreach durch und nehme mir die einzelnen Einträge vor, die werden mit Hilfe der Funktion explode() am Leerzeichen gesplittet, mit preg_replace() von Sonderzeichen bereinigt und mit ctype_upper auf Groß/Kleinschreibung überprüft, ich nehme nur die groß geschriebenen Einträge. Das ist willkürlich festgelegt, produziert aber eine sehr brauchbare Stichwortliste. Schließlich wird der gefundene Eintrag mit array_push() in die Variable $stichwortliste weggeschrieben.

$stichwortliste = array(); 
//Durch alle gefundenen Datensätze durchsteppen
$zaehler = 0;
foreach ( $alleposts as $einpost ) 
{ 
  //ersten gefundenen Titel in array aufsplitten
  $liste = explode(" ", $einpost->post_title);
  
  //Durch das Array durchsteppen
   foreach ($liste as $einwort)
  {
	  //Prüfen, ob Wort groß geschrieben ist
	  $wortanfang = substr($einwort,0,1);
	  
	  //Sonderzeichen entfernen (nach Bedarf editieren)
	  $einwort = preg_replace('/[0-9\@\.\;\" "\(\)\:\?\!\,]+/', '', $einwort);
	  
	  //nur ausgeben wenn Groß geschrieben
	  if (ctype_upper($wortanfang)){
		 
		  $zaehler = $zaehler+1;
		  
		  //Hier kommt der Knackpunkt: Neues Stichwort in Array schreiben
		  //***********************************
		  array_push($stichwortliste, $einwort);
		  //***********************************
	  }// ende von ctype_upper
  }// ende von liste as einwort

  
}//ende von alleposts as einpost und array befüllen

Es folgt noch ein bisschen Kosmetik, und ganz am Ende gibt unsere Funktion das gebrauchsfertige Array zurück:

//Dubletten entfernen
$stichwortliste= array_unique($stichwortliste);

//Array sortieren
sort($stichwortliste);

//Ausgabe Anzahlen erzeugter Stichwörter
echo "Anzahl Stichwörter in den Rohdaten: ".$zaehler."</br>";
echo "Grösse des sortierten und Dubletten-bereinigten Arrays: ".count($stichwortliste)."</br>";
echo "<h2>Erzeuge neue Stichwortliste aus der Tabelle: ".$table_name."</h2>";

return $stichwortliste;
}// ende array erzeugen_function

Noch alle mit mir beieinander? Fehlt noch was? Ach ja, die Erzeugung der CSV-Datei mit Hilfe der Funktion erzeuge_csv(), damit halte ich mich jetzt nicht lange auf, die ist einigermassen selbsterklärend:

function erzeuge_csv($liste){
	
	global $wpdb;
	
	echo "Ich erzeuge jetzt ein csv: ";
	
	//***************
	// Verzeichis des aktuellen Plugins ermitteln
	$dir = plugin_dir_path( __FILE__ );
	$aktVerzeichnis = $dir;
	//Dateiname fest verdrahtet	
	$fileName = $aktVerzeichnis.'stichwortliste.csv';
	echo $fileName."</br>";
	
if(file_exists($fileName)){
	echo "Die alte Datei wird überschrieben</br>";
	}
    
	//Gnadenlos überschreiben, der Parameter 'w' ersetzt den alten Dateiinhalt
	
    $csvFile = fopen($fileName,'w');
    $head = ["Wort"];
    fputcsv($csvFile,$head);

// Variable mit den Listeneinträgen befüllen
foreach ($liste as $einwort){
$data = [
    ["$einwort"],
    
];

//Durch alle data-Einträge durchsteppen und in Datei schreiben

foreach($data as $row){
    fputcsv($csvFile,$row);
}
}
fclose($csvFile);

 //Debug-Ausgabe aller Stichworte
$anzahl = sizeof($liste);
echo "<h2>Testausgabe: ".$anzahl." Stichwörter erzeugt</h2>";

foreach ($liste as $stichwort){
	
	echo $stichwort."</br>";
	
}
		
}// Ende function erzeuge csv
//*****************************************

Falls die Datei nicht existiert, wird sie neu angelegt. Falls sie schon existiert, wird der Inhalt überschrieben. So das wars. Jetzt einen Kaffee und sacken lassen. Und dann Hurra auf zu neuen Ufern, jetzt kommt der Teil mit dem Shortcode. Aber dazu gibts einen neuen Beitrag.

Da knarzt es im Gebälk: ich brauche einen PAP für mein Stichwortverzeichnis

Also, ich hab ja jetzt schon ein paar Tage Gehirnschmalz in das Stichwortverzeichnis investiert, und stelle fest dass die Sache schon hübsch komplex wird. Vor allem muss ich mir Gedanken machen, wer was wann macht (machen darf) und was wohin gehört. Ich mach mal ein Brainstorming:

  • es soll ein WordPress-Plugin (PHP) werden, ich möchte ohne Datenimport/Export in MYSQL arbeiten
  • Auf der WordPress-Adminseite soll es einen neuen Menüpunkt geben: „Stichworttabelle neu erstellen“ . Hier soll erst eine Sicherung der alten Stichworttabelle (falls vorhanden) angelegt werden, dann die Tabelle quelle erst geleert und dann mit der aktuell aus der Tabelle wp_posts (Feld wp_title) neu erstellten Stichwortliste neu befüllt werden.
  • Es soll ein Shortcode mitgeliefert werden [Stichwortverzeichnis], der an beliebiger Stelle auf einer Seite oder in einem Beitrag eingefügt werden kann und der da ein komplettes Stichwortverzeichnis (mit Unterseite) generiert.
  • Es soll eine Positiv- und Negativliste geben, für Wörter die auf jeden Fall/auf keinen Fall im Stichwortverzeichnis auftauchen sollen (nice to have)

Ich seh schon, ich muss mich erst mal wieder mit dem good old WordPress auseinandersetzen, das ist verdammt lang her dass ich was mit Plugins und Admin-Menüs gemacht habe. OK, ich geh mal googlen… bis dann!

Update am Montagmittag: nachdem mir der Webserver noch einige Male unter lautem Jubel abgerauscht ist, schmeisse ich den PAP nochmal über den Haufen. Anscheinend sind die vielen PHP-gesteuerten MySQL-Kommandos zuviel für das gute alte WordPress. Ich machs jetzt ganz anders, ich lege die Stichwörter in eine CSV-Datei. Die sollte man eigentlich gefahrlos wieder auslesen können – ich bin da recht vorsichtig geworden.

Update am Montagabend: Das mit dem CSV war der Schlüssel zum Erfolg, jetzt läuft die ganze Chose stabil. Die Erzeugung einer Stichwortdatei aus den Beitragstiteln der wp_posts läuft einwandfrei, und dauert nicht mal lang. Morgen bastel ich dann den Shortcode, der eben jene CSV-Datei wieder ausliest und das Stichwortverzeichnis an beliebiger Stelle in einem Beitrag oder auf einer Seite aufbaut. Mal sehen ob das ohne grössere Unfälle funkt. Hach, Programmieren ist so eine kurzweilige Sache! 😉

Ein ehrgeiziges Projekt: Stichwortverzeichnis

Meine beiden mit Abstand am häufigsten gebrauchten Kochbücher sind „Das Bayrische Kochbuch“ und „Joy of Cooking“. Und bei beiden ist das am Meisten genutzte Feature das Stichwortverzeichnis, da findet man alles! Es eignet sich auch hervorragend zum kreuz- und querschmökern, man findet dabei Rezepte die man sonst nie gesehen hätte. Langer Rede kurzer Sinn, ein Stichwortverzeichnis (Volltext-Index) wäre ein schickes Feature für mein Inselfisch-Kochbuch. Und dazu möchte ich nicht das Stichwort-Feature von WordPress nutzen, das ist mir viel zu umständlich zu bedienen und zu schlecht auszuwerten.

Dazu waren einige Vorüberlegungen nötig. Zunächst muss ich mal festlegen, woher ich die Stichwörter nehme. Ich habe in meinem Inselfisch-Kochbuch knappe 400 Rezepte, in denen relevanter Text in zwei Feldern steht, einmal in post_title und einmal in post_content. Der Titel ist meist zwischen 5 und 10 Wörter lang, der Content kann wesentlich länger sein, bis zu einer ganzen DIN A 4 Seite und mehr, also auf jeden Fall mehr als 100 Wörter. Ich möchte meine Stichwortbasis natürlich maschinell erzeugen, dazu muss ich irgendwie einzelne Wörter aus meiner Datenbasis extrahieren. Dazu muss ich meinen Quelltext aufsplitten (ob mit PHP oder VBA wird sich noch zeigen) und in eine Tabelle schreiben. Diese Tabelle (ich nenne sie mal Rohdaten) soll am Ende nur alle Stichwörter und eine ID (Autowert) enthalten. Man sieht schon: mit dem Content, das wird uferlos, das können leicht mehrere Zehntausend Wörter werden. Ich beschränke mich also auf die Wörter aus dem post_title, das ist besser zu handeln. Wir machen das mal als erste Annäherung und schauen uns dann an, was wir mit den extrahierten potentiellen Stichworten anfangen können.

Wie ich das in MS Access angehe: ich importiere mir die gesamte Tabelle wp_posts aus dem Original-Inselfischkochbuch über CSV-Export aus phpmyadmin. Dann schreibe ich mir eine Abfrage,, die nur den post_title enthält und als where-Klausel post_Type = post und post_status = publish bekommt, damit ich nur die echt veröffentlichten Rezepte in der Datenbasis habe. Die Abfrage heißt quelle und sieht so aus:

Screenshot quelle

265 Datensätze a drei bis zehn Wörter, damit kann man arbeiten. Ich lege ausserdem fest, dass ich nur Großgeschriebene Wörter im Stichwortverzeichnis haben möchte. Das ist willkürlich, aber doch recht sinnvoll, weil damit die ganzen kleinen Füllwörter rausfallen.

Dann gehts rund: Ich lege mir eine Tabelle namens ziel an, die nur zwei Felder hat: ID(AutoWert) und Wort(Text). Sie bleibt zunächst leer. Dann bastle ich mir ein VBA-Modul. Hier lege ich zwei Recordsets an, rstquelle das ist die Abfrage Quelle, und rstziel, das ist die Tabelle Ziel.

Jetzt gehe ich zum ersten Datensatz in rstquelle und lese mir den Inhalt des Feldes post_title ein. Dann verwende ich die VBA-Funktion split(), die zerlegt das Feld in einzelne Wörter, die in ein Array geschrieben werden.  Ich laufe durch dieses Array und prüfe zunächst, ob das Wort Groß oder klein geschrieben ist:

Asc(Left(liste(i), 1) >= 65) And (Asc(Left(liste(i), 1)) <= 90)

Mit dieser Funktion bin ich nicht so recht glücklich, weil sie unerklärlicherweise manche Wörter mit Kleinbuchstaben doch durchrutschen läßt, aber ich hab noch nichts besseres gefunden.

Dann werden noch evtl vorhandene Sonderzeichen entfernt, dazu gibts eine Funktion die Folgende Zeichenkette durchläuft:

Const strSonderzeichen As String = „.,:;#+’*?=)(/%$§!~\}][{“

Dann schreibe jeweils ein gefundenes, geputztes Wort in die Zieltabelle. Dann gehe ich zum nächsten Datensatz der Quelltabelle und wiederhole den Vorgang. das mache ich, bis EOF der Quelltabelle erreicht ist.

Hurra! 669 Datensätze, beim ersten Drüberschauen sieht es schon mal ganz gut aus. Noch die Dubletten ausblenden, das geht mit einer Abfrage mit Gruppierung ganz easy. Sortieren, man sieht noch ein bisschen Datenschmutz ganz oben, das bereinigt man per Hand.

abfrage quelle

Es bleiben 496 recht manierliche Stichworte übrig. Damit könnte ich jetzt schon nach MySQL und WordPress gehen und die Webseite aufbauen, aber ich halte mich noch ein wenig in Access auf und teste nochmal, mit der „wackeligen“ Asc-Funktion bin ich nicht zufrieden.