4 * Parses AppSteam Data files.
5 * See http://people.freedesktop.org/~hughsient/appdata/
8 * Copyright (c) 2013, Novell Inc.
10 * This program is licensed under the BSD license, read LICENSE.BSD
11 * for further information
14 #include <sys/types.h>
31 #include "repo_appdata.h"
59 /* !! must be sorted by first column !! */
60 static struct stateswitch stateswitches[] = {
61 { STATE_START, "applications", STATE_START, 0 },
62 { STATE_START, "application", STATE_APPLICATION, 0 },
63 { STATE_APPLICATION, "id", STATE_ID, 1 },
64 { STATE_APPLICATION, "licence", STATE_LICENCE, 1 },
65 { STATE_APPLICATION, "product_license", STATE_LICENCE, 1 },
66 { STATE_APPLICATION, "name", STATE_NAME, 1 },
67 { STATE_APPLICATION, "summary", STATE_SUMMARY, 1 },
68 { STATE_APPLICATION, "description", STATE_DESCRIPTION, 0 },
69 { STATE_APPLICATION, "url", STATE_URL, 1 },
70 { STATE_APPLICATION, "project_group", STATE_GROUP, 1 },
71 { STATE_DESCRIPTION, "p", STATE_P, 1 },
72 { STATE_DESCRIPTION, "ul", STATE_UL, 0 },
73 { STATE_DESCRIPTION, "ol", STATE_OL, 0 },
74 { STATE_UL, "li", STATE_UL_LI, 1 },
75 { STATE_OL, "li", STATE_OL_LI, 1 },
91 struct stateswitch *swtab[NUMSTATES];
92 enum state sbtab[NUMSTATES];
109 static inline const char *
110 find_attr(const char *txt, const char **atts)
112 for (; *atts; atts += 2)
113 if (!strcmp(*atts, txt))
120 startElement(void *userData, const char *name, const char **atts)
122 struct parsedata *pd = userData;
123 Pool *pool = pd->pool;
124 Solvable *s = pd->solvable;
125 struct stateswitch *sw;
128 fprintf(stderr, "start: [%d]%s\n", pd->state, name);
130 if (pd->depth != pd->statedepth)
137 if (!pd->swtab[pd->state]) /* no statetable -> no substates */
140 fprintf(stderr, "into unknown: %s (from: %d)\n", name, pd->state);
144 for (sw = pd->swtab[pd->state]; sw->from == pd->state; sw++) /* find name in statetable */
145 if (!strcmp(sw->ename, name))
148 if (sw->from != pd->state)
151 fprintf(stderr, "into unknown: %s (from: %d)\n", name, pd->state);
156 pd->docontent = sw->docontent;
157 pd->statedepth = pd->depth;
163 case STATE_APPLICATION:
164 s = pd->solvable = pool_id2solvable(pool, repo_add_solvable(pd->repo));
165 pd->handle = s - pool->solvables;
171 if (find_attr("xml:lang", atts))
174 case STATE_DESCRIPTION:
176 if (find_attr("xml:lang", atts))
178 pd->description = solv_free(pd->description);
183 if (find_attr("xml:lang", atts))
189 if (find_attr("xml:lang", atts))
195 if (find_attr("xml:lang", atts))
203 /* replace whitespace with one space/newline */
204 /* also strip starting/ending whitespace */
206 wsstrip(struct parsedata *pd)
210 for (i = j = 0; pd->content[i]; i++)
212 if (pd->content[i] == ' ' || pd->content[i] == '\t' || pd->content[i] == '\n')
214 ws |= pd->content[i] == '\n' ? 2 : 1;
218 pd->content[j++] = (ws & 2) ? '\n' : ' ';
220 pd->content[j++] = pd->content[i];
226 /* indent all lines */
228 indent(struct parsedata *pd, int il)
231 for (l = 0; pd->content[l]; )
233 if (pd->content[l] == '\n')
238 if (pd->lcontent + il + 1 > pd->acontent)
240 pd->acontent = pd->lcontent + il + 256;
241 pd->content = realloc(pd->content, pd->acontent);
243 memmove(pd->content + l + il, pd->content + l, pd->lcontent - l + 1);
244 for (i = 0; i < il; i++)
245 pd->content[l + i] = ' ';
247 while (pd->content[l] && pd->content[l] != '\n')
253 add_missing_tags_from_desktop_file(struct parsedata *pd, Solvable *s, const char *desktop_file)
255 Pool *pool = pd->pool;
257 const char *filepath;
262 filepath = pool_tmpjoin(pool, "/usr/share/applications/", desktop_file, 0);
263 if (pd->flags & REPO_USE_ROOTDIR)
264 filepath = pool_prepend_rootdir_tmp(pool, filepath);
265 if (!(fp = fopen(filepath, "r")))
267 while (fgets(buf, sizeof(buf), fp) > 0)
269 int c, l = strlen(buf);
272 if (buf[l - 1] != '\n')
274 /* ignore overlong lines */
275 while ((c = getc(fp)) != EOF)
283 while (l && (buf[l - 1] == ' ' || buf[l - 1] == '\t'))
286 while (*p == ' ' || *p == '\t')
288 if (!*p || *p == '#')
292 if (!strcmp(p, "[Desktop Entry]"))
303 for (p3 = p2 - 1; *p3 == ' ' || *p3 == '\t'; p3--)
306 while (*p2 == ' ' || *p2 == '\t')
310 if (!s->name && !strcmp(p, "Name"))
311 s->name = pool_str2id(pool, pool_tmpjoin(pool, "application:", p2, 0), 1);
312 else if (!pd->havesummary && !strcmp(p, "Comment"))
315 repodata_set_str(pd->data, pd->handle, SOLVABLE_SUMMARY, p2);
319 if (s->name && pd->havesummary)
320 break; /* our work is done */
326 endElement(void *userData, const char *name)
328 struct parsedata *pd = userData;
329 Pool *pool = pd->pool;
330 Solvable *s = pd->solvable;
334 fprintf(stderr, "end: [%d]%s\n", pd->state, name);
336 if (pd->depth != pd->statedepth)
340 fprintf(stderr, "back from unknown %d %d %d\n", pd->state, pd->depth, pd->statedepth);
350 case STATE_APPLICATION:
352 s->arch = ARCH_NOARCH;
355 if ((!s->name || !pd->havesummary) && (pd->flags & APPDATA_CHECK_DESKTOP_FILE) != 0 && pd->desktop_file)
356 add_missing_tags_from_desktop_file(pd, s, pd->desktop_file);
357 if (!s->name && pd->desktop_file)
359 char *name = pool_tmpjoin(pool, "application:", pd->desktop_file, 0);
360 int l = strlen(name);
361 if (l > 8 && !strcmp(".desktop", name + l - 8))
363 s->name = pool_strn2id(pool, name, l, 1);
365 if (s->name && s->arch != ARCH_SRC && s->arch != ARCH_NOSRC)
366 s->provides = repo_addid_dep(pd->repo, s->provides, pool_rel2id(pd->pool, s->name, s->evr, REL_EQ, 1), 0);
368 pd->desktop_file = solv_free(pd->desktop_file);
371 pd->desktop_file = solv_strdup(pd->content);
372 /* guess the appdata.xml file name from the id element */
373 if (pd->lcontent > 8 && !strcmp(".desktop", pd->content + pd->lcontent - 8))
374 pd->content[pd->lcontent - 8] = 0;
375 else if (pd->lcontent > 4 && !strcmp(".ttf", pd->content + pd->lcontent - 4))
376 pd->content[pd->lcontent - 4] = 0;
377 else if (pd->lcontent > 4 && !strcmp(".otf", pd->content + pd->lcontent - 4))
378 pd->content[pd->lcontent - 4] = 0;
379 else if (pd->lcontent > 4 && !strcmp(".xml", pd->content + pd->lcontent - 4))
380 pd->content[pd->lcontent - 4] = 0;
381 else if (pd->lcontent > 3 && !strcmp(".db", pd->content + pd->lcontent - 3))
382 pd->content[pd->lcontent - 3] = 0;
383 id = pool_str2id(pd->pool, pool_tmpjoin(pool, "appdata(", pd->content, ".appdata.xml)"), 1);
384 s->requires = repo_addid_dep(pd->repo, s->requires, id, 0);
385 id = pool_str2id(pd->pool, pool_tmpjoin(pool, "application-appdata(", pd->content, ".appdata.xml)"), 1);
386 s->provides = repo_addid_dep(pd->repo, s->provides, id, 0);
391 s->name = pool_str2id(pd->pool, pool_tmpjoin(pool, "application:", pd->content, 0), 1);
394 repodata_add_poolstr_array(pd->data, pd->handle, SOLVABLE_LICENSE, pd->content);
400 repodata_set_str(pd->data, pd->handle, SOLVABLE_SUMMARY, pd->content);
403 repodata_set_str(pd->data, pd->handle, SOLVABLE_URL, pd->content);
406 repodata_add_poolstr_array(pd->data, pd->handle, SOLVABLE_GROUP, pd->content);
408 case STATE_DESCRIPTION:
409 if (pd->description && !pd->skip_tag_d)
411 /* strip trailing newlines */
412 int l = strlen(pd->description);
413 while (l && pd->description[l - 1] == '\n')
414 pd->description[--l] = 0;
415 repodata_set_str(pd->data, pd->handle, SOLVABLE_DESCRIPTION, pd->description);
422 pd->description = solv_dupappend(pd->description, pd->content, "\n\n");
425 if (pd->skip_tag || pd->skip_tag_li)
429 pd->content[2] = '-';
430 pd->description = solv_dupappend(pd->description, pd->content, "\n");
433 if (pd->skip_tag || pd->skip_tag_li)
437 if (++pd->licnt >= 10)
438 pd->content[0] = '0' + (pd->licnt / 10) % 10;
439 pd->content[1] = '0' + pd->licnt % 10;
440 pd->content[2] = '.';
441 pd->description = solv_dupappend(pd->description, pd->content, "\n");
447 pd->description = solv_dupappend(pd->description, "\n", 0);
453 pd->state = pd->sbtab[pd->state];
457 fprintf(stderr, "end: [%s] -> %d\n", name, pd->state);
463 characterData(void *userData, const XML_Char *s, int len)
465 struct parsedata *pd = userData;
470 l = pd->lcontent + len + 1;
471 if (l > pd->acontent)
473 pd->acontent = l + 256;
474 pd->content = realloc(pd->content, pd->acontent);
476 c = pd->content + pd->lcontent;
483 #define BUFF_SIZE 8192
486 repo_add_appdata(Repo *repo, FILE *fp, int flags)
488 Pool *pool = repo->pool;
490 struct stateswitch *sw;
496 data = repo_add_repodata(repo, flags);
497 memset(&pd, 0, sizeof(pd));
499 pd.pool = repo->pool;
503 pd.content = malloc(256);
506 for (i = 0, sw = stateswitches; sw->from != NUMSTATES; i++, sw++)
508 if (!pd.swtab[sw->from])
509 pd.swtab[sw->from] = sw;
510 pd.sbtab[sw->to] = sw->from;
513 XML_Parser parser = XML_ParserCreate(NULL);
514 XML_SetUserData(parser, &pd);
515 XML_SetElementHandler(parser, startElement, endElement);
516 XML_SetCharacterDataHandler(parser, characterData);
520 l = fread(buf, 1, sizeof(buf), fp);
521 if (XML_Parse(parser, buf, l, l == 0) == XML_STATUS_ERROR)
523 pool_error(pool, -1, "repo_appdata: %s at line %u:%u\n", XML_ErrorString(XML_GetErrorCode(parser)), (unsigned int)XML_GetCurrentLineNumber(parser), (unsigned int)XML_GetCurrentColumnNumber(parser));
530 XML_ParserFree(parser);
532 if (!(flags & REPO_NO_INTERNALIZE))
533 repodata_internalize(data);
535 solv_free(pd.content);
536 solv_free(pd.desktop_file);
537 solv_free(pd.description);
541 /* add all files ending in .appdata.xml */
543 repo_add_appdata_dir(Repo *repo, const char *appdatadir, int flags)
549 data = repo_add_repodata(repo, flags);
550 if (flags & REPO_USE_ROOTDIR)
551 dirpath = pool_prepend_rootdir(repo->pool, appdatadir);
553 dirpath = solv_strdup(appdatadir);
554 if ((dir = opendir(dirpath)) != 0)
556 struct dirent *entry;
557 while ((entry = readdir(dir)))
561 int len = strlen(entry->d_name);
562 if (len <= 12 || strcmp(entry->d_name + len - 12, ".appdata.xml") != 0)
564 if (entry->d_name[0] == '.')
566 n = pool_tmpjoin(repo->pool, dirpath, "/", entry->d_name);
570 pool_error(repo->pool, 0, "%s: %s", n, strerror(errno));
573 repo_add_appdata(repo, fp, flags | REPO_NO_INTERNALIZE | REPO_REUSE_REPODATA | APPDATA_CHECK_DESKTOP_FILE);
579 if (!(flags & REPO_NO_INTERNALIZE))
580 repodata_internalize(data);