КАКОДА користим Property List датотеке
Упутство за програмере. [верзија 0.1, 5. јануар 2024.]
Страхиња РадићСадржај
1. Увод
Property List или plist датотеке су датотеке са подешавањима у текстуалном или бинарном формату. Новија текстуална верзија plist формата је у ствари XML, али ни она, ни бинарни формат нису предмет овог текста. Уместо тога, овде ће реч бити о првој, текстуалној, верзији тог формата, која је коришћена у оперативном систему NeXTSTEP, па је зато усвојена и у менаџеру прозора Window Maker. Једино „упутство“ које сам за сада могао да пронађем је оно из туторијала на сајту WINGs библиотеке, a оно се састоји од простог набрајања једног дела свих функција, међу којима су и оне које се баве plist‑ом. Ту лежи основни разлог за писање овог упутства.2. Формат Property List датотека
Пример самог plist формата су датотеке са подешавањима, обично у директоријуму $HOME/GNUstep/Defaults, рецимо $HOME/GNUstep/Defaults/WMState:
{ Dock = { AutoRaiseLower = No; Applications = ( { Forced = No; Name = Logo.WMDock; BuggyApplication = No; AutoLaunch = No; Position = "0,0"; Lock = No; Command = "-"; }, { Forced = No; Name = wmclock.WMClock; DropCommand = "wmclock %d"; BuggyApplication = No; AutoLaunch = Yes; Position = "0,2"; Lock = Yes; PasteCommand = "wmclock %s"; Command = wmclock; }, //... }; //... }
или $HOME/GNUstep/Defaults/WMGLOBAL:
{ BoldSystemFont = "Liberation Sans:slant=0:weight=200:width=100:pixelsize=12"; SystemFont = "Liberation Sans:slant=0:weight=80:width=100:pixelsize=12"; }
или, рецимо, /usr/local/share/WindowMaker/plmenu:
("Applications", ("Info", ("Info Panel", INFO_PANEL), ("Legal", LEGAL_PANEL), ("System Console", EXEC, "xconsole"), ("System Load", SHEXEC, "xosview || xload"), ("Process List", EXEC, "xterm -e top"), ("Manual Browser", EXEC, "xman") ), //... )
итд. Одавде можемо већ да закључимо неколико ствари:
Подаци су организовани у низове (Array):
(један, два, три)
и/или „речнике“ (Dictionary, хеш листе), који се састоје од вредности индексираних кључевима:
{ боја = црвена; година = 2024; }
Приметимо да се у речницима свака „додела“ мора завршити тачка‑зарезом.Сви „скаларни“ подаци су представљени искључиво двама типовима: нискама знакова (String), по потреби заштићеним наводницима, или бајтовима представљеним у хексадекадном запису са по две хексадекадне цифре за један бајт, и окруженим угластим заградама („подаци“, Data). На пример:
<cafebabe>
представља низ од 4 бајта:0xCA
,0xFE
,0xBA
и0xBE
(али је, рецимо,<4A3>
грешка због непарног броја цифара). Ово значи и да је бројеве или логичке вредности потребно тумачити у самом програму, независно од библиотеке WINGs.
Рецимо, plist
{ Applications = ({ }, { }); }
је речник са једним кључем, Applications, чија вредност је низ од два (празна) речника.
3. Читање датотеке
plist датотеке се, наравно, могу налазити било где у систему датотека, али
WINGs садржи и згодну функцију wusergnusteppath
, која враћа путању
корисничког директоријума $HOME/GNUstep. На пример:
#include <WINGs/WINGs.h> #include <stdio.h> #include <stdlib.h> int main() { char prefsfile[PATH_MAX]; WMPropList* settings; snprintf(prefsfile, PATH_MAX, "%s/%s", wusergnusteppath(), "Defaults/wpltest"); if (!(settings = WMReadPropListFromFile(prefsfile))) { fprintf(stderr, "Cannot read plist file %sn", prefsfile); exit(1); } // ... Манипулација добијеним plist-ом у променљивој settings ... WMReleasePropList(settings); return 0; }
пошто датотека $HOME/GNUstep/Defaults/wpltest (највероватније) не постоји, програм ће исписати поруку о грешци. Међутим, и ако извршимо:
$ touch $HOME/GNUstep/Defaults/wpltest $ ./wpltest Cannot read plist file /home/strajder/GNUstep/Defaults/wpltest
Празна датотека није валидан plist, па WMReadPropListFromFile
и даље враћа
NULL
. Убацимо зато у њу празан речник:
$ printf "{}\n" > $HOME/GNUstep/Defaults/wpltest $ ./wpltest $
3.1. Испитивање типа plist-а
Видели смо да је сваки plist или низ, или речник, ниска знакова или податак.
На срећу, WINGs садржи низ функција за испитивање типа plist‑а. Ако
програму додамо функцију PLToString
која враћа тип plist‑а на основу
испитивања овим функцијама:
const char* PLToString(WMPropList* pl) { if (WMIsPLString(pl)) return "String"; else if (WMIsPLData(pl)) return "Data"; else if (WMIsPLArray(pl)) return "Array"; else if (WMIsPLDictionary(pl)) return "Dictionary"; else return "Error"; }
и уместо коментара у нашем тест‑програму убацимо:
printf("type(settings) = %s\n", PLToString(settings));
добићемо:
$ cat $HOME/GNUstep/Defaults/wpltest {} $ ./wpltest type(settings) = Dictionary
3.2. Приступ вредностима у речнику и низовима
Изменимо сада датотеку wpltest:
$ printf "{ Sequence = < 1b A0B1c2 >; Amount = 42; }\n" > $HOME/GNUstep/Defaults/wpltest
и додајмо после позива функције printf
у нашем програму:
if (WMIsPLDictionary(settings)) { WMPropList* keys; keys = WMGetPLDictionaryKeys(settings); printf("type(keys) = %sn", PLToString(keys)); for (int i = 0; i < WMGetPropListItemCount(keys); i++) { WMPropList* key; WMPropList* val; char* skey; char* sval; key = WMGetFromPLArray(keys, i); printf("type(key = keys[%d]) = %s\n", i, PLToString(key)); skey = WMGetFromPLString(key); printf("key = %s\n", skey); val = WMGetFromPLDictionary(settings, key); printf("type(val = settings{%s}) = %s\n", skey, PLToString(val)); // ... наставак следи ... } WMReleasePropList(keys); }
овде прво испитујемо да ли је settings
речник, и уколико јесте, прослеђујемо
ту променљиву функцији WMGetPLDictionaryKeys
, која враћа „plist‑низ“ свих
кључева тог речника. Даље се врши итерација тог низа, где је горња граница
итератора одређена позивом функције WMGetPropListItemCount
над низом keys
.
Пошто функција WMGetPLDictionaryKeys
алоцира меморију, добра је пракса да на
крају блока у ком смо је позвали позовемо и функцију WMReleasePropList
, како
бисмо ослободили ту алоцирану меморију.
За сваку вредност итератора, i‑ти члан низа keys
добијамо као резултат
позива функције WMGetFromPLArray(keys, i)
. Ово можемо да схватимо као
еквивалент
key = keys[i];
До конкретног назива кључа key
се не може доћи директно, већ се мора позвати
функција WMGetFromPLString
. Она се може схватити као cast‑овање:
skey = (String)key;
Слично са вредношћу која је индексирана кључем key
. Њу добијамо позивом
функције WMGetFromPLDictionary(settings, key)
, што се може схватити као када
бисмо у Perl‑у написали
$settings{$key}
где је %settings
хеш.
Ако сада покренемо програм, добићемо:
$ cat $HOME/GNUstep/Defaults/wpltest { Sequence = < 1b A0B1c2 >; Amount = 42; } $ ./wpltest type(settings) = Dictionary type(keys) = Array type(key = keys[0]) = String key = Amount type(val = settings{Amount}) = String type(key = keys[1]) = String key = Sequence type(val = settings{Sequence}) = Data
Приметимо да вредности у оквиру keys
не морају бити у истом поретку као у
датотеци. Такође је, сасвим у складу са оним што је речено, тип вредности 42
,
индексиране са Amount
, ниска знакова, а не број. За даље коришћење те
вредности бисмо по свој прилици морали да позовемо функцију типа strtol
. Тај
начин претварања ниске знакова у број ћемо видети у практичном примеру у
следећој тачки.
3.3. Приступ подацима и нискама знакова
Технички, управо смо већ обрадили приступ нискама знакова позивом функције
WMGetFromPLString
. Погледајмо сада како да приступимо и plist типу
„податак“, и да конвертујемо ниске знакова у бројеве.
Додајмо на почетак нашег програма
#include <errno.h>
а уместо коментара:
if (WMIsPLData(val)) { const unsigned char* data; const unsigned char* pdata; data = WMGetPLDataBytes(val); pdata = data; printf("data(val) = "); while (*pdata) { printf("0x%02X ", *pdata); pdata++; } printf("\n"); } else if (WMIsPLString(val)) { long lval = 0; sval = WMGetFromPLString(val); errno = 0; lval = strtol(sval, NULL, 10); if (!lval && (errno == EINVAL || errno == ERANGE)) printf("string(val) = %sn", sval); else printf("number(val) = %ld\n", lval); }
у суштини, позивом функције WMGetPLDataBytes
добијамо показивач који показује
на секвенцу const unsigned char
‑ова, завршену NUL
бајтом. Простом
итерацијом тог низа приступамо појединачним бајтовима.
Вредност индексирану кључем Amount
покушавамо да конвертујемо функцијом
strtol
, и у случају неуспеха закључујемо да се ради о знаковној нисци, а не
броју.
Сада наш програм исписује додатне информације:
$ ./wpltest type(settings) = Dictionary type(keys) = Array type(key = keys[0]) = String key = Amount type(val = settings{Amount}) = String number(val) = 42 type(key = keys[1]) = String key = Sequence type(val = settings{Sequence}) = Data data(val) = 0x1B 0xA0 0xB1 0xC2
4. Писање у plist датотеку
Писање у датотеку може се извести директно, позивом функције
WMWritePropListToFile
, којој се проследи plist променљива (у случају да је
већ имамо спремну) и назив датотеке. Међутим, за практичне примене вероватно ће
интересантнији бити случај када се та променљива конструише „у лету“, од
„изворног кода“. Ово се постиже функцијом WMCreatePropListFromDescription
:
#define MAX_PL_BUF 512 int save_prefs(const long amount, const unsigned char* sequence) { char desc[MAX_PL_BUF]; char sfile[PATH_MAX]; WMPropList* settings = NULL; char* ssequence = wmalloc(strlen((const char*)sequence) * 2 + 1); char* pssequence = ssequence; const unsigned char* psequence = sequence; while (*psequence) { snprintf(pssequence, 3, "%02X", *psequence); pssequence += 2; psequence++; } snprintf(desc, MAX_PL_BUF, "{ Sequence = < %s >; " " Amount = %ld; }", ssequence, amount); snprintf(sfile, PATH_MAX, "%s/%s", wusergnusteppath(), "Defaults/wpltest-new"); if (!(settings = WMCreatePropListFromDescription(desc))) { fprintf(stderr, "WMCreatePropListFromDescription failed\n"); wfree(ssequence); return 1; } if (!WMWritePropListToFile(settings, sfile)) { fprintf(stderr, "WMWritePropListToFile failed\n"); wfree(ssequence); WMReleasePropList(settings); return 1; } wfree(ssequence); WMReleasePropList(settings); return 0; }
(наравно, сада нам на почетку кода треба и string.h).
Функције wmalloc
/wfree
су помоћне WINGs функције, које по потреби користе
Boehm GC, a wmalloc
и тестира да ли је меморија успешно
алоцирана, па због тога то не морамо да радимо у функцији save_prefs
.
Уз пар малих измена (додавање декларације long amount
и постављање вредности
те променљиве из lval
када се наиђе на !strcmp(skey, "Amount")
, програму се
може додати и позив
save_prefs(amount, data);
па сада имамо:
$ cat $HOME/GNUstep/Defaults/wpltest { Sequence = < 1b A0B1c2 >; Amount = 42; } $ ./wpltest >/dev/null $ cat $HOME/GNUstep/Defaults/wpltest-new { Amount = 42; Sequence = <1ba0b1c2>; }
коначна верзија кода је дата испод.
Датотека wpltest.c (дугачак листинг)
#include <WINGs/WINGs.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
const char*
PLToString(WMPropList* pl)
{
if (WMIsPLString(pl))
return "String";
else if (WMIsPLData(pl))
return "Data";
else if (WMIsPLArray(pl))
return "Array";
else if (WMIsPLDictionary(pl))
return "Dictionary";
else
return "Error";
}
#define MAX_PL_BUF 512
int
save_prefs(const long amount, const unsigned char* sequence)
{
char desc[MAX_PL_BUF];
char sfile[PATH_MAX];
WMPropList* settings = NULL;
char* ssequence = wmalloc(strlen((const char*)sequence) * 2 + 1);
char* pssequence = ssequence;
const unsigned char* psequence = sequence;
while (*psequence)
{
snprintf(pssequence, 3, "%02X", *psequence);
pssequence += 2;
psequence++;
}
snprintf(desc, MAX_PL_BUF,
"{ Sequence = < %s >; "
" Amount = %ld; }",
ssequence,
amount);
snprintf(sfile, PATH_MAX, "%s/%s", wusergnusteppath(),
"Defaults/wpltest-new");
if (!(settings = WMCreatePropListFromDescription(desc)))
{
fprintf(stderr, "WMCreatePropListFromDescription failed\n");
wfree(ssequence);
return 1;
}
if (!WMWritePropListToFile(settings, sfile))
{
fprintf(stderr, "WMWritePropListToFile failed\n");
wfree(ssequence);
WMReleasePropList(settings);
return 1;
}
wfree(ssequence);
WMReleasePropList(settings);
return 0;
}
int
main()
{
char prefsfile[PATH_MAX];
WMPropList* settings;
long amount = 0;
const unsigned char* sequence = NULL;
snprintf(prefsfile, PATH_MAX - 1, "%s/%s", wusergnusteppath(),
"Defaults/wpltest");
if (!(settings = WMReadPropListFromFile(prefsfile)))
{
fprintf(stderr, "Cannot read plist file %s\n", prefsfile);
exit(1);
}
printf("type(settings) = %s\n", PLToString(settings));
if (WMIsPLDictionary(settings))
{
WMPropList* keys;
keys = WMGetPLDictionaryKeys(settings);
printf("type(keys) = %s\n", PLToString(keys));
for (int i = 0; i < WMGetPropListItemCount(keys); i++)
{
WMPropList* key;
WMPropList* val;
char* skey;
char* sval;
key = WMGetFromPLArray(keys, i);
printf("type(key = keys[%d]) = %s\n", i,
PLToString(key));
skey = WMGetFromPLString(key);
printf("key = %s\n", skey);
val = WMGetFromPLDictionary(settings, key);
printf("type(val = settings{%s}) = %s\n", skey,
PLToString(val));
if (WMIsPLData(val))
{
const unsigned char* data;
const unsigned char* pdata;
data = WMGetPLDataBytes(val);
pdata = data;
printf("data(val) = ");
while (*pdata)
{
printf("0x%02X ", *pdata);
pdata++;
}
printf("\n");
if (!strcmp(skey, "Sequence"))
sequence = data;
}
else if (WMIsPLString(val))
{
long lval = 0;
sval = WMGetFromPLString(val);
errno = 0;
lval = strtol(sval, NULL, 10);
if (!lval
&& (errno == EINVAL || errno == ERANGE))
printf("string(val) = %s\n", sval);
else
printf("number(val) = %ld\n", lval);
if (!strcmp(skey, "Amount"))
amount = lval;
}
}
WMReleasePropList(keys);
if (save_prefs(amount, sequence))
fprintf(stderr, "save_prefs failed\n");
}
WMReleasePropList(settings);
return 0;
}