\chapter{Hilfsbibliothek libCUI-util}

Die libCUI-util ist eine Sammlung von Hilfsroutinen, die direkt oder indirekt
f"ur die Programmierung von CUI-Programmen n"utzlich sein k"onnen. Zudem finden
sich hier eisfair-spezifische Funktionen zum Umgang mit Konfigurationsdateien
und anderen administrativen Aufgaben. Keine der beiden Bibliotheken ist von
der anderen abh"angig, was bedeutet, dass Programme die Hilfsbibliothek verwenden
k"onnen, ohne die libCUI zu verwenden zu m"ussen. Dies ist umgekehrt ebenso.

\section{Verwenden der libCUI-util}

Wie auch die libCUI, so kann auch die libCUI-util dynamisch und statisch gelinkt
werden.

Dynamisch:
\begin{verbatim}
gcc -Wall -Wstrict-prototypes -o test test.c -lcui-util
\end{verbatim}

Statisch:
\begin{verbatim}
gcc -o test test.c /usr/lib/libcui-util.a
\end{verbatim}




\section{XML-Parser}

Der hier enthaltene Implementierung ist ein einfacher Parser, der XML-formatierte
Dateien in eine Baumstruktur einliest. Der eingelesene Baum, der aus Knoten, Objekten
und Attributen besteht, kann anschlie"send im Speicher durchsucht und ausgewertet
werden. Zudem besteht die M"oglichkeit, den eingelesenen Baum zu modifizieren und
in modifizierter Form wieder in eine Datei zu schreiben.

In den Datei ''cui-util.h'' sind die folgenden Funktionsprototypen f"ur den XML-Parser
definiert:

\begin{verbatim}
XMLFILE* XmlCreate  (const char* filename);

void XmlDelete      (XMLFILE* xml);

void XmlSetErrorHook(XMLFILE* xml, 
                     ErrorCallback errout,
                     void* instance);
                     
void XmlAddSingleTag(XMLFILE* xml, 
                     const char* name);

int XmlReadFile     (XMLFILE* xml);

int XmlWriteFile    (XMLFILE* xml);

void XmlPreserveNewline(XMLFILE* xml, int state);

XMLOBJECT* XmlGetObjectTree(XMLFILE* xml);

\end{verbatim}

Mit der Funktion ''XmlCreate'' wird eine Instanz der Stuktur ''XMLFILE'' angelegt und 
dieser der Dateiname ''filename'' zugewiesen. Nun kann mit ''XmlReadFile'' die Datei
eingelesen und mit ''XmlWriteFile'' wieder zur"uckgeschrieben werden. Die
R"uckgabewerte sind endweder ''TRUE'' oder ''FALSE'' je nachdem, ob die Funktion 
erfolgreich abgeschlossen werden konnte oder nicht. Freigegeben wird die Datenstruktur
''XMLFILE'' sowie der gesamte Objektbaum mit Hilfe der Funktion ''XmlDelete''.

Die Funktion ''XmlPreserveNewline'' sorgt beim Einlesen der Datei daf"ur, dass
Zeilenumbr"uche erhalten bleiben, sich die Formatierung des Textes in der
ASCII-Datei auch in den Daten des Objektbaums wiederfindet. Standardm"a"sig
werden Zeilenumbr"uche ignoriert.

L"auft der Parser beim Lesen der Datei auf einen Fehler, dann wird dieser "uber eine
Callback-Funktion ausgegeben, die dem Parser zuvor "uber die Funktion
''XmlSetErrorHook'' mitgeteilt werden muss. Der Paramter ''instance'' kann dabei
optional eine Fensterinstanz erhalten und ist speziell f"ur das Zusammenspiel
mit der libCUI gedacht. Die Callback-Funktion muss von Typ ''ErrorCallback'' sein,
die den folgenden Prototypen vorgibt:

\begin{verbatim}
typedef void (*ErrorCallback)(void* instance,
                              const char* errmsg,
                              const char* filename,
                              int line,
                              int is_warning);
\end{verbatim}

Ein Code-Schnipsel, das eine XML-Datei einliest, kann nun wie folgt aussehen:

\begin{verbatim}
   XMLFILE* xmldata = XmlCreate("myfile.xml");
   XMLOBJECT* rootobj;
   if (xmldata)
   {
      XmlSetErrorHook(xmldata, MyErrorHook, win);
      if (XmlReadFile(xmldata))
      {
         rootobj = XmlGetObjectTree(xmldata);
         
         /* do something with the data */
         
      }
      XmlDelete(xmldata);
   }
\end{verbatim}

Die Implementierung der Callback-Funktion ist je nach Programm und Art der
Fehlerbehandlung verschieden. Hier ein m"ogliches Beispiel:

\begin{verbatim}
void
MyErrorHook(void* w, const char* errmsg, const char* filename,
             int linenr, int is_warning)
{
   CUIWINDOW* win = (CUIWINDOW*) w;
   if (win)
   {
      MYWINDATA* data = (MYWINDATA*) win->InstData;

      if ((data->NumErrors + data->NumWarnings) < 8)
      {
         char err[512];
         if (is_warning)
         {
            sprintf(err,"WARNING: (%i): %s",linenr,errmsg);
                        MywinAddMessage(win, err);
         }
         else
         {
            sprintf(err,"ERROR: (%i): %s",linenr,errmsg);
                        MywinAddMessage(win, err);
         }
      }
      else if ((data->NumErrors + data->NumWarnings) == 8)
      {
         MywinAddMessage(win, "... more errors");
      }

      if (is_warning)
      {
         data->NumWarnings++;
      }
      else
      {
         data->NumErrors++;
      }
   }
}
\end{verbatim}

"uber ''MywinAddMessage'' wird die aktuelle Fehlermeldung zu einer
Liste hinzugef"ugt (begrenzt auf max. acht Fehler). Nach Abschluss der 
Leseoperation kann sp"ater die Liste der Fehlermeldungen in einem
Mitteilungsfenster (z.B. MessageBox) ausgegeben werden.

\begin{figure}
  \includegraphics[width=6in]{xml1.png}
\end{figure}

Die Abbildung zeigt, wie sich eine einfache XML-Datei im Objektbaum
darstellt. Dieser besteht aus Objekten, Knoten, Attributen und Daten.
Ausgehend vom stets vorhandenen Wurzelobjekt verl"auft eine Liste
von Knoten. Knoten k"onnen drei Typen besitzen: 

XML\_COMMENTNODE\\
Der Knoten steht f"ur einen Kommentar. Das Datenfeld "Data" zeigt auf
auf einen Kommentartext.

XML\_OBJNODE\\
Der Knoten zeigt auf ein Objekt. Objekte besitzen selbst keine Daten, verf"ugen 
aber ihrerseits wieder "uber eine Liste von Knoten.

XML\_DATANODE\\
Der Knoten steht f"ur einen Block von Daten. Das Datenfeld "Data" zeigt auf
auf eine Zeichenkette mit den enthaltenen Daten.

Die Elemente des Objektbaums bestehen aus den folgenden Datentypen:


\begin{verbatim}

typedef struct
{
   char* Name;       /* name of XML attribute */
   char* Value;      /* name of attribute value */
   void* Next;       /* next XML attribute */
} XMLATTRIBUTE;

typedef struct
{
   int   Type;       /* Type of node: data or child object */
   char* Data;       /* Data (if node is a data node) */
   int   DataLen;    /* Size of current data buffer */
   void* Object;     /* Pointer to child object (if object node) */

   void* Next;       /* Next node or NULL */
} XMLNODE;

typedef struct
{
   char* Name;              /* Name of XML object */

   XMLNODE*      FirstNode; /* The first node containing 
                               data or a child object */
   XMLNODE*      LastNode;  /* The last node containing 
                               data or a child object */
   XMLATTRIBUTE* FirstAttr; /* Pointer to first object attribute */
   XMLATTRIBUTE* LastAttr;  /* Pointer to last object attribute */
} XMLOBJECT;

\end{verbatim}

Der Objektbaum kann nach dem Einlesen beliebig modifiziert werden. Die API
Funktionen (deren Parameter weitestgehend selbsterkl"arend sein sollten)
sind hier aufgelistet:

\begin{verbatim}

XMLOBJECT* XmlCreateObject    (XMLFILE* xml, XMLOBJECT* parent);
void       XmlDeleteObject    (XMLFILE* xml, XMLOBJECT* ob);
void       XmlSetObjectName   (XMLOBJECT* ob, const char* name);
void       XmlSetObjectData   (XMLOBJECT* ob, const char* data);
void       XmlAddObjectData   (XMLOBJECT* ob, const char* data);
void       XmlSetObjectComment(XMLOBJECT* ob, const char* data);
void       XmlAddObjectComment(XMLOBJECT* ob, const char* data);

XMLATTRIBUTE* XmlCreateAttribute  (XMLOBJECT* ob,
                                   const char* name);
void          XmlSetAttributeValue(XMLATTRIBUTE* attr, 
                                   const char* name);
XMLATTRIBUTE* XmlGetAttribute     (XMLOBJECT* ob, 
                                   const char* name);

\end{verbatim}



\section{Konfigurations-Parser}

Der Konfigurationsparser ist ein Parser, der einfach aufgebaute 
Konfigurationsdateien der folgenden Form einliest:

\begin{verbatim}
FOO_NAME='Name'
FOO_PATH='/var/lib/foo'
FOO_ENUM_N='2'
FOO_ENUM_1_VALUE='1234'
FOO_ENUM_2_VALUE='0'
\end{verbatim}

Die unter eifair gebr"auchliche Form der Konfigurationsdateien sieht
Wertepaare vor, bei denen der Wert in einfachen oder doppelten
Anf"uhrungsstrichen steht. Es k"onnen auch ein- oder mehrdimensionale
Arrays aufgebaut werden, indem Optionen einem Aufz"ahlungsknoten 
(im Beispiel FOO\_ENUM\_N) zugeordnet werden.

\hinweis{Dieser Parser ist dazu gedacht, Konfigurationsdateien
    zu lesen, wie sie z.B. von CUI-Programmen unter eisfair verwendet
    werden (z.B. /etc/cui.conf). Der Nutzen des Parsers zum Einlesen
    von Paketkonfigurationen (z.B. /etc/config.d/foo) ist jedoch recht 
    eingeschr"ankt, da Infrastrukturdateien (z.B. /etc/check.d/foo) nicht 
    ber"ucksichtigt werden. Dem Programm muss die Struktur der
    Konfigurationsdatei bekannt sein.}

Der Parser bringt die folgende API zum Lesen der Dateien mit:

\begin{verbatim}

CONFIG*  ConfigOpen     (ErrorCallback errout, 
                         void* instance);
void     ConfigAddNode  (CONFIG* cfg, 
                         const char* n_node, 
                         const char* mask);
void     ConfigReadFile (CONFIG* cfg, 
                         const char* filename);
void     ConfigClose    (CONFIG* clg);

\end{verbatim}

Der Aufruf von ConfigOpen bereitet das Lesen der Datei vor, indem 
eine ''CONFIG'' Struktur allokiert wird und dieser eine Callback-
Funktion f"ur Fehlerausgaben zugewiesen wird. Das Prinzip und der
Funktionsprototyp der Callback- Funktion sind mit denen des XML-Parsers
(siehe Abschnitt ''XML-Parser'') identisch.

Mit der Funktion ''ConfigAddNode'' wird dem Parser mitgeteilt, welche
Optionen Array-Elemente sind und welchem Aufz"ahlungsknoten sie zugeordnet 
werden. Der Parser lernt auf diese Weise die Struktur der Datei.
Der Parameter ''n\_node'' gibt dabei den Namen des Aufz"ahlungsknotens an,
der Parameter ''mask'' enth"alt eine Maske zur Erkennung des Optionsnamens.

Mit ''ConfigReadFile'' wird die Datei eingelesen. Es ist dabei zu 
beachten, dass die Funktion selbst keine Auskunft "uber Erfolg oder
Misserfolg dibt. Fehler werden statt dessen "uber die Callback- Funktion
ausgegeben und k"onnen hier ggf. gez"ahlt werden. 

Die Funktion ''ConfigClose'' gibt die ''CONFIG'' Struktur und alle damit
verbundenen Daten wieder frei.

\begin{figure}
  \includegraphics[width=6in]{config1.png}
\end{figure}

Nach dem Einlesen steht die Konfigurationsdatei als Wertebaum im Speicher 
zur Verf"ugung. Mit den folgenden API-Funktionen kann sie nun ausgewertet
werden:

\begin{verbatim}

CONFENTRY*  ConfigGetEntry (CONFIG* cfg, 
                            CONFENTRY* parent, 
                            const char* name, 
                            int * index);
const char* ConfigGetString(CONFIG* cfg, 
                            CONFENTRY* parent, 
                            const char* name,
                            OPT_TYPE type, 
                            const char* defval, 
                            int * index);
int         ConfigGetBool  (CONFIG* cfg, 
                            CONFENTRY* parent, 
                            const char* name,
                            OPT_TYPE type, 
                            const char* defval, 
                            int * index);
int         ConfigGetNum   (CONFIG* cfg, 
                            CONFENTRY* parent, 
                            const char* name,
                            OPT_TYPE type, 
                            const char* defval, 
                            int * index);
                            
\end{verbatim}

Die Funktionen dienen dem Auslesen unterschiedlicher Typen aus der Datei.
Die Parameter haben dabei immmer die gleiche Bedeutung: 

''cfg'':    CONFIG-Struktur der zuvor eingelesenen Datei.

''parent'': Aufz"ahlungsknoten oder NULL. 

''name'':   Name der Option.

''type'':   Handelt es sich um einen optionalen Wert? 

''defval'': Standardwert, falls die Option nicht gefunden wird.

''index'':  Array mit Indexwerten im Falle von Arrays. Die Anzahl der
Indexwerte im Array muss dabei der Dimension des Arrayelements ''Option''
entsprechen.

Bis auf ''ConfigGetEntry'' geben die Funktionen immer einen Wert zur"uck.
Wird die Option nicht gefunden, dann wird der Standardwert zur"uckgegeben.
Allerdings kann mit dem Parameter ''type'' (type = REQUIRED) vorgegeben
werden, dass die Option existieren muss. In diesem Fall wird eine
Meldung "uber die Callback- Funktion ausgegeben, falls der Wert nicht
gefunden wurde.

Zum Lesen der oben aufgef"uhrten Datei kann nun der folgende Code
verwendet werden:

\begin{verbatim}

MYDATA* data = (MYDATA*) win->InstData;
CONFIG* cfg = ConfigOpen(ErrOut, win); 
if (cfg)
{
   CONFENTRY* p;
   int i, index;
   
   /* reset error counter */
   data->NumErrors = 0;
   
   /* define file structure and read file */
   ConfigAddNode(cfg, "FOO_ENUM_N", "FOO_ENUM_%_VALUE");
   ConfigReadFile(cfg, "/etc/myconf.conf");
   
   /* get array dimensions */
   index = ConfigGetNum(cfg, NULL, "FOO_ENUM_N", 
                        REQUIRED, "0", NULL);
                        
   /* read array */
   p = ConfigGetEntry(cfg, NULL, "FOO_ENUM_N", NULL);
   if (p)
   {
      for (i = 0; i < index; i++)
      {
         val = ConfigGetNum(cfg, p, "FOO_ENUM_%_VALUE", &i);
		  
         /* do something with "val" */
      }
   }
   ConfigClose(cfg);
  
   if (data->NumErrors > 0)
   {
      /* display errors */
   }
}

\end{verbatim}


   
\section{Co-Prozesse ausf"uhren}

Es ist oftmals erforderlich, andere Programme im Hintergrund zu starten
und deren Textausgaben oder Ergebniswerte anschlie"send weiterzuverarbeiten.
Damit lassen sich Aufgaben an andere Programme delegieren, Schnittstellen
zu Scripten etc. schaffen und vorhandene Funktionalit"at nutzen.

Die libCUI-util stellt eine API-Funktion zur Verf"ugung, mit der Co-Prozesse
ausgef"uhrt werden k"onnen:

\begin{verbatim}

int  RunCoProcess(const char* filename,
                  char* const parameters[],
                  TextCall callback,
                  int* exitcode);

\end{verbatim}

Der Parameter ''filename'' gibt den Namen und den Pfad des externen
Programms an, das Array ''parameters'' enth"alt die Programmargumente,
''callback'' ist eine Callback-Funktion f"ur die Textausgaben des Programms
und ''exitcode'' ist die Variable die den R"uckgabewert des aufgerufenen
Programms zur"uckgibt. Der R"uckgabewert der Funktion ''RunCoProcess''
ist ''TRUE'' wenn der Aufruf erfolgreich war und ''FALSE'' falls nicht.

Bei ''parameters'' ist zu ber"ucksichtigen, dass das erste Element (parameters[0])
den Namen des Programms erhalten muss. Das letzte Element enth"alt einen Nullzeiger
und signalisiert damit das Ende der Liste.

Die Funktion vom Typ ''TextCall'' muss dem folgenden Prototypen entsprechen:

\begin{verbatim}

typedef void (*TextCall) (const char* buffer, int source);

\end{verbatim}

Sie erh"alt die Textausgaben des Programms zeilenweise (also immer dann, wenn
ein Zeilenumbruch vom Programm zur"uckgegeben wird. Der Parameter ''source''
kann die Werte ''PIPE\_STDOUT'' und ''PIPE\_STDERR'' annehmen und gibt damit
an, "uber welchen Augabekanal der Text ausgegeben wurde.

Die Funktion ''RunCoProcess'' h"alt das Programm im Vordergrund an, bis der
CoProcess beendet wurde.

Ein Beispiel:

\begin{verbatim}

void RunCallback(const char* buffer, int source)
{
   if (source == PIPE_STDOUT)
   {
      printf("INFO:  %s\n", buffer);
   }
   else
   {
      printf("ERROR: %s\n", buffer);
   }
}

int RunScript(const char* scriptfile)
{
   int result, status;
   const char* p[3];
   
   /* initialize for interpreted scripts */
   p[0] = "/bin/bash";
   p[1] = scriptfile;
   p[2] = 0;

   /* execute */
   result = RunCoProcess("/bin/bash", (char**) p, 
                         RunCallback, &status);
   
   /* evaluate result */
   return (result == TRUE) && (status == 0);
}

\end{verbatim}

ToDo: Hier sollte noch ein Instanzzeiger enthalten sein, damit dieser an die
Callback-Routine "ubergeben werden kann.

Ein Hinweis sein hier auch nochmal auf das Terminal Kontrollelement der
libCUI gegeben. Dieses erlaubt die Ausf"uhrung von Programmen in einem
Terminal-Fenster, w"ahrend der Rest der Anwendung normal weiterl"auft.
