Hlavní navigace

GLib: Automatické doplňování řetězců

Michal Burda

Líbí se vám, jak třeba příkazový procesor "uhádne" konec příkazu poté, co napíšete pár prvních znaků a stisknete tabulátor? Chtěli byste podobnou vlastnost zakomponovat do svých programů? Proč vymýšlet již vymyšlené - používejte knihovnu GLib a její automatické doplňování řetězců!

Mechanizmus pro automatické doplňování řetězců stojí na datové struktuře GCompletion (viz níže), na kterou se volají všechny obslužné rutiny. K tomu, abyste mohli mechanizmus automatického doplňování řetězců používat, je nutné, abyste byli obeznámeni alespoň se základy práce s obousměrnými seznamy, o kterých se na Rootu psalo v minulých dvou dílech.

Doplňování řetězců funguje následovně. Speciálními funkcemi v  GCompletion u nejprve „zaregistrujete“ všechny možné (a smysluplné) řetězce. Voláním funkce g_completion_complete() si pak vyžádáte uhodnutí konce řetězce k zadanému „začátku“ (prefixu – řetězci, který je zadán jako argument). Funkce prohledá seznam všech řetězců, vyhodnotí, jak by asi měl pokračovat a vrátí seznam všech vhodných adeptů na doplnění a popřípadě i nový prefix.

Podívejme se nyní trochu blíže na onu strukturu GCompletion:

struct GCompletion
{
  GList* items;
  GCompletionFunc func;

  gchar* prefix;
  GList* cache;
};
items 
prefix 

Vytvoření nové struktury GCompletion

Abychom mechanizmus automatického doplňování řetězců mohli používat, je nutné nejprve vytvořit v paměti samotnou strukturu GCompletion. Provede se to příkazem

GCompletion* g_completion_new(GCompletionFunc func); 

Funkce požaduje jako argument konverzní funkci func a vrátí pointer na nový  GCompletion.

Funkce func je dána předpisem

gchar* (*GCompletionFunc)(gpointer); 

Očekává se od ní, že jako jediný argument převezme gpointer na data uložená v obousměrném seznamu a najde v nich řetězec, který předá jako svou návratovou hodnotu. Při takové práci musíte dbát na to, aby všechny řetězce, které funkce func zprostředkovává, byly neustále v paměti. Mechanizmus ve funkci g_completion_complete() to totiž pro správnou činnost vyžaduje. Nejsnáze se toho dosáhne tak, že v datech uložených v seznamu jsou přímo ony řetězce obsaženy a funkce func pak pouze k těmto datům přistupuje (viz příklad na konci kapitoly). To, že by se řetězce při každém volání funkce func teprve dynamicky tvořily a následně dealokovaly, je nepřípustné.

Budou-li v seznamu uloženy přímo řetězce, může být argument func funkce g_completion_new()   NULL ový.

Práce se seznamem „registrovaných“ položek

Se seznamem registrovaných položek (tedy s položkou items struktury GCompletion) se pracuje pomocí trojice následujících funkcí. Všechny tři požadují jako svůj první parametr cmp pointer na strukturu  GCompletion.

void g_completion_add_items(GCompletion *cmp, GList *items); 

…přidá mezi registrované prvky cmp->items GCompletion u cmp  prvky seznamu items. Předchozí výsledky doplňování řetězce se zruší – tedy dealokují a na NULL se nastaví cmp->prefixcmp->cache.

Přidávání prvků proběhne tak, že se vytvoří kopie uzlů GList seznamu do kterých se zkopírují pointery na jejich data. Vyplývá z toho tedy to, že seznam items musíte po použití sami dealokovat. Pozor však na data, které budou mezi oběma seznamy sdílené. Musíte zajistit, aby po celou dobu, kdy je mechanizmus doplňování řetězců může potřebovat, existovaly.

Potřebujete-li ze seznamu registrovaných prvků cmp->items naopak položky odstraňovat, použijte funkci

void g_completion_remove_items(GCompletion *cmp, GList
*items); 

Tato funkce prohledá seznam registrovaných prvků ( cmp->items) i seznam adeptů na doplnění řetězce ( cmp->cache) a odstraní z nich všechny prvky, jejichž data jsou v seznamu předaném jako parametr items. Odstraněním prvků se rozumí dealokace uzlů GList  – s uloženými daty se nebude dít nic.

A konečně poslední funkce, která je velmi jednoduchá:

void g_completion_clear_items(GCompletion *cmp); 

…jejím úkolem je odstranit všechny registrované položky a inicializovat GCompletion cmp, aby byl takový, jaký je po svém vytvoření – dealokují se celé seznamy cmp->items i cmp->cache  stejně tak jako prefix cmp->prefix. (Opět, aby bylo jasno: dealokací seznamu se rozumí vyprázdnění seznamu, tedy volání g_list_free()  – samotná data zůstanou v paměti nedotčena!)

Doplnění řetězce

Funkce

GList* g_completion_complete(GCompletion *cmp, gchar *prefix,
        gchar **new_prefix);

je klíčovou funkcí celého mechanizmu. Je to ona, kdo „odedře“ celou práci.

Jako první argument jí předejte pointer na GCompletion ( cmp) a jako druhý argument „začátek“ řetězce neboli prefix, ke kterému se bude provádět doplňování.

Funkce g_completion_complete() vrátí seznam všech prvků, jejichž řetězce začínají zadaným prefix em a do třetího argumentu ( new_prefix) uloží nejdelší možný „začátek“ řetězce, který je společný pro všechny prvky shodující se se zadaným prefix em nebo NULL, jestliže nenalezne jedinou položku, která by začínala prefix em.

Vrácený seznam je určen jen ke čtení a řetězec new_prefix by měl být po použití dealokován.

Dealokace GCompletion

void g_completion_free(GCompletion *cmp); 

…uvolní veškerou paměť použitou GCompletion em cmp včetně jeho seznamů. Datové položky seznamů však musíte dealokovat sami.

Příklad:

/* Priklad pouziti automatickeho doplnovani retezcu */

#include <glib.h>

/* Datova struktura (abychom to nemeli tak jednoduche) */
typedef struct {
  gchar *s;
  gint value;  /* Nejake dalsi polozky... */
} MyData;

/* Konverzni funkce */
gchar* konverze(gpointer data)
{
  return ((MyData *) data)->s;
}

/* Funkce pro vytvoreni dynamickych dat */
gpointer data_new(gchar* s, gint value)
{
  MyData* data;

  data = g_new(MyData, 1);
  data->s = s;
  data->value = value;
  return (gpointer) data;
}

/* Tisk */
void data_print(gpointer data, gpointer user_data)
{
  printf("%s - %d\n", ((MyData *) data)->s,
    ((MyData *) data)->value);
}

/* dealokace dat */
void data_dealloc(gpointer data, gpointer user_data)
{
  g_free(data);
}

/* Hlavni program */
gint main(void)
{
  GCompletion* cmp;
  GList* list = NULL;
  GList* completed;
  gchar* new_prefix;

  /* Vytvoreni noveho GCompletion */
  cmp = g_completion_new(konverze);

  /* Registrace nejakych polozek */
  list = g_list_append(list, data_new("dopis.txt", 2));
  list = g_list_append(list, data_new("dluznici.txt", 8));
  list = g_list_append(list, data_new("smlouva.txt", 13));
  list = g_list_append(list, data_new("doporuceni.txt", 1));
  list = g_list_append(list, data_new("dopis.gif", 6));
  g_completion_add_items(cmp, list);

  /* Doplneni retezce */
  completed = g_completion_complete(cmp, "k", &new_prefix);
  printf("Maximalni prefix: %s\n", new_prefix);
  g_list_foreach(completed, data_print, NULL);
  g_free(new_prefix);

  /* Doplneni retezce (podruhe) */
  completed = g_completion_complete(cmp, "do", &new_prefix);
  printf("\nMaximalni prefix: %s\n", new_prefix);
  g_list_foreach(completed, data_print, NULL);
  g_free(new_prefix);

  /* Doplneni retezce (potreti) */
  completed = g_completion_complete(cmp, "dopo", &new_prefix);
  printf("\nMaximalni prefix: %s\n", new_prefix);
  g_list_foreach(completed, data_print, NULL);
  g_free(new_prefix);

  /* Uvolneni pameti */
  g_completion_free(cmp);
  g_list_foreach(list, data_dealloc, NULL);
  g_list_free(list);

  return 0;
}

Spustíte-li si tuto ukázku, mělo by se vám vypsat:

Maximalni prefix: (null)

Maximalni prefix: dop
dopis.txt - 2
doporuceni.txt - 1
dopis.gif - 6

Maximalni prefix: doporuceni.txt
doporuceni.txt - 1

Co to znamená, si jistě pilný čtenář prostuduje sám.

Jakékoliv dotazy a připomínky prosím směřujte na mou adresu.

Našli jste v článku chybu?