/* mRss - Copyright (C) 2005-2007 baku - Andrea Marchesini
 *                                     <baku@ippolita.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#else
#error Use configure; make; make install
#endif

#include "mrss.h"
#include "mrss_internal.h"

static void __mrss_parse_tag_insert(mrss_tag_t **where, mrss_tag_t *what) {
  if (!*where)
    *where = what;
  else {
    mrss_tag_t *tag = *where;

    while (tag->next)
      tag = tag->next;

    tag->next = what;
  }
}

static mrss_tag_t *__mrss_parse_tag(nxml_t *doc, nxml_data_t *cur) {
  mrss_tag_t *tag;
  mrss_attribute_t *attribute;
  nxml_attr_t *nxml_attr;

  if (!(tag = (mrss_tag_t *)calloc(1, sizeof(mrss_tag_t))))
    return NULL;

  tag->element = MRSS_ELEMENT_TAG;
  tag->allocated = 1;

  if (!(tag->name = strdup(cur->value))) {
    mrss_free(tag);
    return NULL;
  }

  if (cur->ns && cur->ns->ns && !(tag->ns = strdup(cur->ns->ns))) {
    mrss_free(tag);
    return NULL;
  }

  for (nxml_attr = cur->attributes; nxml_attr; nxml_attr = nxml_attr->next) {

    if (!(attribute = (mrss_attribute_t *)calloc(1, sizeof(mrss_attribute_t))))
      return NULL;

    attribute->element = MRSS_ELEMENT_ATTRIBUTE;
    attribute->allocated = 1;

    if (!(attribute->name = strdup(nxml_attr->name))) {
      mrss_free(tag);
      return NULL;
    }

    if (!(attribute->value = strdup(nxml_attr->value))) {
      mrss_free(tag);
      return NULL;
    }

    if (nxml_attr->ns && nxml_attr->ns->ns &&
        !(attribute->ns = strdup(nxml_attr->ns->ns))) {
      mrss_free(tag);
      return NULL;
    }

    if (!tag->attributes)
      tag->attributes = attribute;
    else {
      mrss_attribute_t *tmp = tag->attributes;

      while (tmp->next)
        tmp = tmp->next;

      tmp->next = attribute;
    }
  }

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_TEXT) {
      if (!tag->value && !(tag->value = strdup(cur->value))) {
        mrss_free(tag);
        return NULL;
      }
    } else if (cur->type == NXML_TYPE_ELEMENT) {
      mrss_tag_t *child = __mrss_parse_tag(doc, cur);

      if (child)
        __mrss_parse_tag_insert(&tag->children, child);
    }
  }

  return tag;
}

static void __mrss_parser_atom_string(nxml_t *doc, nxml_data_t *cur,
                                      char **what, char **type) {
  char *c;

  if (!(c = nxmle_find_attribute(cur, "type", NULL)) || !strcmp(c, "text")) {
    *what = nxmle_get_string(cur, NULL);
    *type = c;
    return;
  }

  if (!strcmp(c, "html") || !strcmp(c, "xhtml")) {
    nxml_data_t *ncur;
    char *total, *c1;
    nxml_t *new;
    int size;

    total = NULL;
    size = 0;

    c1 = nxmle_get_string(cur, NULL);

    if (c1 && *c1) {
      total = c1;
      size = strlen(total);
    }

    else {
      free(c1);
      while ((ncur = cur->children)) {
        char *buffer = NULL, *p;
        char *tmp;
        int len;

        if (nxml_remove(doc, cur, ncur) != NXML_OK)
          continue;

        if (nxml_new(&new) != NXML_OK) {
          nxml_free_data(ncur);
          continue;
        }

        if (nxml_add(new, NULL, &ncur) != NXML_OK) {
          nxml_free_data(ncur);
          nxml_free(new);
          continue;
        }

        if (!(buffer = nxmle_write_buffer(new, NULL))) {
          nxml_free(new);
          continue;
        }

        nxml_free(new);

        if (strncmp(buffer, "<?xml ", 6)) {
          free(buffer);
          continue;
        }

        p = buffer;

        while (*p && *p != '>')
          p++;

        if (!*p) {
          free(buffer);
          continue;
        }

        p++;
        while (*p && (*p == ' ' || *p == '\t' || *p == '\n'))
          p++;

        len = strlen(p);

        if (!(tmp = realloc(total, size + len + 1))) {
          free(buffer);

          if (total) {
            free(total);
            total = NULL;
          }

          break;
        }

        total = tmp;
        strcpy(total + size, p);
        size += len;

        free(buffer);
      }
    }

    *what = total;
    *type = c;
    return;
  }

  free(c);
  *what = nxmle_get_string(cur, NULL);
}

static char *__mrss_atom_prepare_date(mrss_t *data, char *datestr) {
  struct tm stm;

  if (!datestr)
    return NULL;

  memset(&stm, 0, sizeof(stm));

  /* ISO8601 Local TZ timestamp: 2018-04-04T09:27:41CEST */
  if (strptime(datestr, "%Y-%m-%dT%T%Z", &stm) != NULL) {
    char datebuf[256];

#ifdef USE_LOCALE
    if (!data->c_locale &&
        !(data->c_locale = newlocale(LC_ALL_MASK, "C", NULL)))
      return NULL;

    strftime_l(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S %z", &stm,
               data->c_locale);
#else
    strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S %z", &stm);
#endif

    return strdup(datebuf);
  }

  return NULL;
}

static void __mrss_parser_atom_category(nxml_data_t *cur,
                                        mrss_category_t **category) {
  char *c;
  mrss_category_t *cat;

  if (!(cat = calloc(1, sizeof(mrss_category_t))))
    return;

  if (!(c = nxmle_find_attribute(cur, "term", NULL))) {
    free(cat);
    return;
  }

  cat->element = MRSS_ELEMENT_CATEGORY;
  cat->allocated = 1;
  cat->category = c;

  if ((c = nxmle_find_attribute(cur, "scheme", NULL)))
    cat->domain = c;

  if ((c = nxmle_find_attribute(cur, "label", NULL)))
    cat->label = c;

  if (!*category)
    *category = cat;

  else {
    mrss_category_t *tmp;
    tmp = *category;

    while (tmp->next)
      tmp = tmp->next;

    tmp->next = cat;
  }
}

static void __mrss_parser_atom_author(nxml_data_t *cur, char **name,
                                      char **email, char **uri) {
  for (cur = cur->children; cur; cur = cur->next) {
    if (!*name && !strcmp(cur->value, "name"))
      *name = nxmle_get_string(cur, NULL);

    else if (!*email && !strcmp(cur->value, "email"))
      *email = nxmle_get_string(cur, NULL);

    else if (!*uri && !strcmp(cur->value, "uri"))
      *uri = nxmle_get_string(cur, NULL);
  }
}

static void __mrss_parser_atom_entry(nxml_t *doc, nxml_data_t *cur,
                                     mrss_t *data) {
  char *c;
  mrss_item_t *item;

  if (!(item = malloc(sizeof(mrss_item_t))))
    return;

  memset(item, 0, sizeof(mrss_item_t));
  item->element = MRSS_ELEMENT_ITEM;
  item->allocated = 1;

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      /* title -> title */
      if (!item->title && !strcmp(cur->value, "title"))
        __mrss_parser_atom_string(doc, cur, &item->title, &item->title_type);

      /* link href -> link */
      else if (!item->link && !strcmp(cur->value, "link") &&
               (c = nxmle_find_attribute(cur, "href", NULL)))
        item->link = c;

      /* content -> description */
      else if (!item->description && !strcmp(cur->value, "content"))
        __mrss_parser_atom_string(doc, cur, &item->description,
                                  &item->description_type);

      /* summary -> description */
      else if (!item->description && !strcmp(cur->value, "summary"))
        __mrss_parser_atom_string(doc, cur, &item->description,
                                  &item->description_type);

      /* right -> copyright */
      else if (!item->copyright && !strcmp(cur->value, "rights"))
        __mrss_parser_atom_string(doc, cur, &item->copyright,
                                  &item->copyright_type);

      /* author structure -> author elements */
      else if (!strcmp(cur->value, "author"))
        __mrss_parser_atom_author(cur, &item->author, &item->author_email,
                                  &item->author_uri);

      /* contributor structure -> contributor elements */
      else if (!strcmp(cur->value, "contributor"))
        __mrss_parser_atom_author(cur, &item->contributor,
                                  &item->contributor_email,
                                  &item->contributor_uri);

      /* published -> pubDate */
      else if (!item->pubDate && !strcmp(cur->value, "published") &&
               data->version == MRSS_VERSION_ATOM_1_0 &&
               (c = nxmle_get_string(cur, NULL))) {
        item->pubDate = __mrss_atom_prepare_date(data, c);
        free(c);
      }

      else if (!item->pubDate && !strcmp(cur->value, "updated") &&
               data->version == MRSS_VERSION_ATOM_1_0 &&
               (c = nxmle_get_string(cur, NULL))) {
        item->pubDate = __mrss_atom_prepare_date(data, c);
        free(c);
      }

      /* issued -> pubDate (Atom 0.3) */
      else if (!item->pubDate && !strcmp(cur->value, "issued") &&
               (c = nxmle_get_string(cur, NULL))) {
        item->pubDate = __mrss_atom_prepare_date(data, c);
        free(c);
      }

      /* id -> guid */
      else if (!item->guid && !strcmp(cur->value, "id") &&
               (c = nxmle_get_string(cur, NULL)))
        item->guid = c;

      /* categories */
      else if (!strcmp(cur->value, "category"))
        __mrss_parser_atom_category(cur, &item->category);

      else {
        mrss_tag_t *tag;
        if ((tag = __mrss_parse_tag(doc, cur)))
          __mrss_parse_tag_insert(&item->other_tags, tag);
      }
    }
  }

  if (!data->item)
    data->item = item;

  else {
    mrss_item_t *tmp = data->item;

    while (tmp->next)
      tmp = tmp->next;

    tmp->next = item;
  }
}

static void __mrss_parser_rss_image(nxml_t *doc, nxml_data_t *cur,
                                    mrss_t *data) {
  char *c;

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      /* title */
      if (!strcmp(cur->value, "title") && !data->image_title &&
          (c = nxmle_get_string(cur, NULL)))
        data->image_title = c;

      /* url */
      else if (!strcmp(cur->value, "url") && !data->image_url &&
               (c = nxmle_get_string(cur, NULL)))
        data->image_url = c;

      /* link */
      else if (!strcmp(cur->value, "link") && !data->image_link &&
               (c = nxmle_get_string(cur, NULL)))
        data->image_link = c;

      /* width */
      else if (!strcmp(cur->value, "width") && !data->image_width &&
               (c = nxmle_get_string(cur, NULL))) {
        data->image_width = atoi(c);
        free(c);
      }

      /* height */
      else if (!strcmp(cur->value, "height") && !data->image_height &&
               (c = nxmle_get_string(cur, NULL))) {
        data->image_height = atoi(c);
        free(c);
      }

      /* description */
      else if (!strcmp(cur->value, "description") && !data->image_description &&
               (c = nxmle_get_string(cur, NULL)))
        data->image_description = c;
    }
  }
}

static void __mrss_parser_rss_textinput(nxml_t *doc, nxml_data_t *cur,
                                        mrss_t *data) {
  char *c;

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      /* title */
      if (!strcmp(cur->value, "title") && !data->textinput_title &&
          (c = nxmle_get_string(cur, NULL)))
        data->textinput_title = c;

      /* description */
      else if (!strcmp(cur->value, "description") &&
               !data->textinput_description &&
               (c = nxmle_get_string(cur, NULL)))
        data->textinput_description = c;

      /* name */
      else if (!strcmp(cur->value, "name") && !data->textinput_name &&
               (c = nxmle_get_string(cur, NULL)))
        data->textinput_name = c;

      /* link */
      else if (!strcmp(cur->value, "link") && !data->textinput_link &&
               (c = nxmle_get_string(cur, NULL)))
        data->textinput_link = c;
    }
  }
}

static void __mrss_parser_rss_skipHours(nxml_t *doc, nxml_data_t *cur,
                                        mrss_t *data) {
  char *c;

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      if (!strcmp(cur->value, "hour") && (c = nxmle_get_string(cur, NULL))) {
        mrss_hour_t *hour;

        if (!(hour = (mrss_hour_t *)calloc(1, sizeof(mrss_hour_t)))) {
          free(c);
          return;
        }

        hour->element = MRSS_ELEMENT_SKIPHOURS;
        hour->allocated = 1;
        hour->hour = c;

        if (!data->skipHours)
          data->skipHours = hour;
        else {
          mrss_hour_t *tmp;

          tmp = data->skipHours;

          while (tmp->next)
            tmp = tmp->next;
          tmp->next = hour;
        }
      }
    }
  }
}

static void __mrss_parser_rss_skipDays(nxml_t *doc, nxml_data_t *cur,
                                       mrss_t *data) {
  char *c;

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      if (!strcmp(cur->value, "day") && (c = nxmle_get_string(cur, NULL))) {
        mrss_day_t *day;

        if (!(day = (mrss_day_t *)calloc(1, sizeof(mrss_day_t)))) {
          free(c);
          return;
        }

        day->element = MRSS_ELEMENT_SKIPDAYS;
        day->allocated = 1;
        day->day = c;

        if (!data->skipDays)
          data->skipDays = day;
        else {
          mrss_day_t *tmp;

          tmp = data->skipDays;

          while (tmp->next)
            tmp = tmp->next;
          tmp->next = day;
        }
      }
    }
  }
}

static void __mrss_parser_rss_item(nxml_t *doc, nxml_data_t *cur,
                                   mrss_t *data) {
  char *c;
  char *attr;
  mrss_item_t *item;

  if (!(item = (mrss_item_t *)calloc(1, sizeof(mrss_item_t))))
    return;

  item->element = MRSS_ELEMENT_ITEM;
  item->allocated = 1;

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      /* title */
      if (!strcmp(cur->value, "title") && !item->title &&
          (c = nxmle_get_string(cur, NULL)))
        item->title = c;

      /* link */
      else if (!strcmp(cur->value, "link") && !item->link &&
               (c = nxmle_get_string(cur, NULL)))
        item->link = c;

      /* description */
      else if (!strcmp(cur->value, "description") && !item->description &&
               (c = nxmle_get_string(cur, NULL)))
        item->description = c;

      /* source */
      else if (!strcmp(cur->value, "source") && !item->source) {
        item->source = nxmle_get_string(cur, NULL);

        if ((attr = nxmle_find_attribute(cur, "url", NULL)))
          item->source_url = attr;
      }

      /* enclosure */
      else if (!strcmp(cur->value, "enclosure") && !item->enclosure) {
        item->enclosure = nxmle_get_string(cur, NULL);

        if ((attr = nxmle_find_attribute(cur, "url", NULL)))
          item->enclosure_url = attr;

        if ((attr = nxmle_find_attribute(cur, "length", NULL))) {
          item->enclosure_length = atoi(attr);
          free(attr);
        }

        if ((attr = nxmle_find_attribute(cur, "type", NULL)))
          item->enclosure_type = attr;
      }

      /* category */
      else if (!strcmp(cur->value, "category") &&
               (c = nxmle_get_string(cur, NULL))) {
        mrss_category_t *category;

        if (!(category =
                  (mrss_category_t *)calloc(1, sizeof(mrss_category_t)))) {
          free(c);
          return;
        }

        category->element = MRSS_ELEMENT_CATEGORY;
        category->allocated = 1;
        category->category = c;

        if ((attr = nxmle_find_attribute(cur, "domain", NULL)))
          category->domain = attr;

        if (!item->category)
          item->category = category;
        else {
          mrss_category_t *tmp;

          tmp = item->category;
          while (tmp->next)
            tmp = tmp->next;
          tmp->next = category;
        }
      }

      /* author */
      else if (!strcmp(cur->value, "author") && !item->author &&
               (c = nxmle_get_string(cur, NULL)))
        item->author = c;

      /* comments */
      else if (!strcmp(cur->value, "comments") && !item->comments &&
               (c = nxmle_get_string(cur, NULL)))
        item->comments = c;

      /* guid */
      else if (!strcmp(cur->value, "guid") && !item->guid &&
               (c = nxmle_get_string(cur, NULL))) {
        item->guid = c;

        if ((attr = nxmle_find_attribute(cur, "isPermaLink", NULL))) {
          if (!strcmp(attr, "false"))
            item->guid_isPermaLink = 0;
          else
            item->guid_isPermaLink = 1;

          free(attr);
        }
      }

      /* pubDate */
      else if (!strcmp(cur->value, "pubDate") && !item->pubDate &&
               (c = nxmle_get_string(cur, NULL)))
        item->pubDate = c;

      /* Other tags: */
      else {
        mrss_tag_t *tag;

        if ((tag = __mrss_parse_tag(doc, cur)))
          __mrss_parse_tag_insert(&item->other_tags, tag);
      }
    }
  }

  if (!data->item)
    data->item = item;
  else {
    mrss_item_t *tmp;

    tmp = data->item;

    while (tmp->next)
      tmp = tmp->next;
    tmp->next = item;
  }
}

static mrss_error_t __mrss_parser_atom(nxml_t *doc, nxml_data_t *cur,
                                       mrss_t **ret) {
  mrss_t *data;
  char *c = NULL;

  if (!(data = malloc(sizeof(mrss_t))))
    return MRSS_ERR_POSIX;

  memset(data, 0, sizeof(mrss_t));
  data->element = MRSS_ELEMENT_CHANNEL;
  data->allocated = 1;
  data->version = MRSS_VERSION_ATOM_1_0;

  if (doc->encoding && !(data->encoding = strdup(doc->encoding))) {
    mrss_free(data);
    return MRSS_ERR_POSIX;
  }

  if (!data->language && (c = nxmle_find_attribute(cur, "xml:lang", NULL)))
    data->language = c;

  if ((c = nxmle_find_attribute(cur, "version", NULL))) {
    if (!strcmp(c, "0.3"))
      data->version = MRSS_VERSION_ATOM_0_3;

    free(c);
  }

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      /* title -> title */
      if (!data->title && !strcmp(cur->value, "title"))
        __mrss_parser_atom_string(doc, cur, &data->title, &data->title_type);

      /* subtitle -> description */
      else if (!data->description && data->version == MRSS_VERSION_ATOM_1_0 &&
               !strcmp(cur->value, "subtitle"))
        __mrss_parser_atom_string(doc, cur, &data->description,
                                  &data->description_type);

      /* tagline -> description (Atom 0.3) */
      else if (data->version == MRSS_VERSION_ATOM_0_3 && !data->description &&
               !strcmp(cur->value, "tagline"))
        __mrss_parser_atom_string(doc, cur, &data->description,
                                  &data->description_type);

      /* link href -> link */
      else if (!strcmp(cur->value, "link") && !data->link &&
               (c = nxmle_find_attribute(cur, "href", NULL)))
        data->link = c;

      /* id -> id */
      else if (!strcmp(cur->value, "id") && !data->id &&
               (c = nxmle_get_string(cur, NULL)))
        data->id = c;

      /* rights -> copyright */
      else if (!data->copyright && !strcmp(cur->value, "rights"))
        __mrss_parser_atom_string(doc, cur, &data->copyright,
                                  &data->copyright_type);

      /* updated -> lastBuildDate */
      else if (!strcmp(cur->value, "updated") &&
               (c = nxmle_get_string(cur, NULL))) {
        data->lastBuildDate = __mrss_atom_prepare_date(data, c);
        free(c);
      }

      /* author -> managingeditor */
      else if (!strcmp(cur->value, "author"))
        __mrss_parser_atom_author(cur, &data->managingeditor,
                                  &data->managingeditor_email,
                                  &data->managingeditor_uri);

      /* contributor */
      else if (!strcmp(cur->value, "contributor"))
        __mrss_parser_atom_author(cur, &data->contributor,
                                  &data->contributor_email,
                                  &data->contributor_uri);

      /* generator -> generator */
      else if (!strcmp(cur->value, "generator") && !data->generator &&
               (c = nxmle_get_string(cur, NULL))) {
        char *attr;

        data->generator = c;

        if ((attr = nxmle_find_attribute(cur, "uri", NULL)))
          data->generator_uri = attr;

        if ((attr = nxmle_find_attribute(cur, "version", NULL)))
          data->generator_version = attr;
      }

      /* icon -> image_url */
      else if (!strcmp(cur->value, "icon") && !data->image_url &&
               (c = nxmle_get_string(cur, NULL)))
        data->image_url = c;

      /* logo -> image_logo */
      else if (!strcmp(cur->value, "logo") && !data->image_logo &&
               (c = nxmle_get_string(cur, NULL)))
        data->image_logo = c;

      /* category */
      else if (!strcmp(cur->value, "category"))
        __mrss_parser_atom_category(cur, &data->category);

      /* entry -> item */
      else if (!strcmp(cur->value, "entry"))
        __mrss_parser_atom_entry(doc, cur, data);

      else {
        mrss_tag_t *tag;
        if ((tag = __mrss_parse_tag(doc, cur)))
          __mrss_parse_tag_insert(&data->other_tags, tag);
      }
    }
  }

  *ret = data;

  return MRSS_OK;
}

static mrss_error_t __mrss_parser_rss(mrss_version_t v, nxml_t *doc,
                                      nxml_data_t *cur, mrss_t **ret) {
  mrss_t *data;
  char *c, *attr;

  if (!(data = (mrss_t *)calloc(1, sizeof(mrss_t))))
    return MRSS_ERR_POSIX;

  data->element = MRSS_ELEMENT_CHANNEL;
  data->allocated = 1;
  data->version = v;

  if (doc->encoding && !(data->encoding = strdup(doc->encoding))) {
    mrss_free(data);
    return MRSS_ERR_POSIX;
  }

  if (data->version == MRSS_VERSION_1_0) {
    nxml_data_t *cur_channel = NULL;

    while (cur) {

      if (!strcmp(cur->value, "channel"))
        cur_channel = cur;

      else if (!strcmp(cur->value, "image"))
        __mrss_parser_rss_image(doc, cur, data);

      else if (!strcmp(cur->value, "textinput"))
        __mrss_parser_rss_textinput(doc, cur, data);

      else if (!strcmp(cur->value, "item"))
        __mrss_parser_rss_item(doc, cur, data);

      cur = cur->next;
    }

    cur = cur_channel;
  } else {
    while (cur && strcmp(cur->value, "channel"))
      cur = cur->next;
  }

  if (!cur) {
    mrss_free(data);
    return MRSS_ERR_PARSER;
  }

  if (data->version == MRSS_VERSION_1_0) {
    if ((attr = nxmle_find_attribute(cur, "about", NULL)))
      data->about = attr;
  }

  for (cur = cur->children; cur; cur = cur->next) {
    if (cur->type == NXML_TYPE_ELEMENT) {
      /* title */
      if (!strcmp(cur->value, "title") && !data->title &&
          (c = nxmle_get_string(cur, NULL)))
        data->title = c;

      /* description */
      else if (!strcmp(cur->value, "description") && !data->description &&
               (c = nxmle_get_string(cur, NULL)))
        data->description = c;

      /* link */
      else if (!strcmp(cur->value, "link") && !data->link &&
               (c = nxmle_get_string(cur, NULL)))
        data->link = c;

      /* language */
      else if (!strcmp(cur->value, "language") && !data->language &&
               (c = nxmle_get_string(cur, NULL)))
        data->language = c;

      /* rating */
      else if (!strcmp(cur->value, "rating") && !data->rating &&
               (c = nxmle_get_string(cur, NULL)))
        data->rating = c;

      /* copyright */
      else if (!strcmp(cur->value, "copyright") && !data->copyright &&
               (c = nxmle_get_string(cur, NULL)))
        data->copyright = c;

      /* pubDate */
      else if (!strcmp(cur->value, "pubDate") && !data->pubDate &&
               (c = nxmle_get_string(cur, NULL)))
        data->pubDate = c;

      /* lastBuildDate */
      else if (!strcmp(cur->value, "lastBuildDate") && !data->lastBuildDate &&
               (c = nxmle_get_string(cur, NULL)))
        data->lastBuildDate = c;

      /* docs */
      else if (!strcmp(cur->value, "docs") && !data->docs &&
               (c = nxmle_get_string(cur, NULL)))
        data->docs = c;

      /* managingeditor */
      else if (!strcmp(cur->value, "managingeditor") && !data->managingeditor &&
               (c = nxmle_get_string(cur, NULL)))
        data->managingeditor = c;

      /* webMaster */
      else if (!strcmp(cur->value, "webMaster") && !data->webMaster &&
               (c = nxmle_get_string(cur, NULL)))
        data->webMaster = c;

      /* image */
      else if (!strcmp(cur->value, "image"))
        __mrss_parser_rss_image(doc, cur, data);

      /* textinput */
      else if (!strcmp(cur->value, "textinput"))
        __mrss_parser_rss_textinput(doc, cur, data);

      /* skipHours */
      else if (!strcmp(cur->value, "skipHours"))
        __mrss_parser_rss_skipHours(doc, cur, data);

      /* skipDays */
      else if (!strcmp(cur->value, "skipDays"))
        __mrss_parser_rss_skipDays(doc, cur, data);

      /* item */
      else if (!strcmp(cur->value, "item"))
        __mrss_parser_rss_item(doc, cur, data);

      /* category */
      else if (!strcmp(cur->value, "category") &&
               (c = nxmle_get_string(cur, NULL))) {
        mrss_category_t *category;

        if (!(category =
                  (mrss_category_t *)calloc(1, sizeof(mrss_category_t)))) {
          mrss_free((mrss_generic_t *)data);
          free(c);
          return MRSS_ERR_POSIX;
        }

        category->element = MRSS_ELEMENT_CATEGORY;
        category->allocated = 1;
        category->category = c;

        if ((attr = nxmle_find_attribute(cur, "domain", NULL)))
          category->domain = attr;

        if (!data->category)
          data->category = category;
        else {
          mrss_category_t *tmp;

          tmp = data->category;
          while (tmp->next)
            tmp = tmp->next;
          tmp->next = category;
        }
      }

      /* enclosure */
      else if (!strcmp(cur->value, "cloud") && !data->cloud) {
        data->cloud = nxmle_get_string(cur, NULL);

        if (!data->cloud_domain &&
            (attr = nxmle_find_attribute(cur, "domain", NULL)))
          data->cloud_domain = attr;

        if (!data->cloud_port &&
            (attr = nxmle_find_attribute(cur, "port", NULL))) {
          data->cloud_port = atoi(attr);
          free(attr);
        }

        if (!data->cloud_registerProcedure &&
            (attr = nxmle_find_attribute(cur, "registerProcedure", NULL)))
          data->cloud_registerProcedure = attr;

        if (!data->cloud_protocol &&
            (attr = nxmle_find_attribute(cur, "protocol", NULL)))
          data->cloud_protocol = attr;
      }

      /* generator */
      else if (!strcmp(cur->value, "generator") && !data->generator &&
               (c = nxmle_get_string(cur, NULL)))
        data->generator = c;

      /* ttl */
      else if (!strcmp(cur->value, "ttl") && !data->ttl &&
               (c = nxmle_get_string(cur, NULL))) {
        data->ttl = atoi(c);
        free(c);
      }

      /* Other tags: */
      else if (data->version != MRSS_VERSION_1_0 ||
               strcmp(cur->value, "items")) {
        mrss_tag_t *tag;

        if ((tag = __mrss_parse_tag(doc, cur)))
          __mrss_parse_tag_insert(&data->other_tags, tag);
      }
    }
  }

  *ret = data;

  return MRSS_OK;
}

static mrss_error_t __mrss_parser(nxml_t *doc, mrss_t **ret) {
  mrss_error_t r = MRSS_ERR_VERSION;
  nxml_data_t *cur;
  char *c;

  if (!(cur = nxmle_root_element(doc, NULL)))
    return MRSS_ERR_PARSER;

  if (!strcmp(cur->value, "rss")) {
    if ((c = nxmle_find_attribute(cur, "version", NULL))) {
      /* 0.91 VERSION */
      if (!strcmp(c, "0.91"))
        r = __mrss_parser_rss(MRSS_VERSION_0_91, doc, cur->children, ret);

      /* 0.92 VERSION */
      else if (!strcmp(c, "0.92"))
        r = __mrss_parser_rss(MRSS_VERSION_0_92, doc, cur->children, ret);

      /* 2.0 VERSION */
      else if (!strcmp(c, "2.0"))
        r = __mrss_parser_rss(MRSS_VERSION_2_0, doc, cur->children, ret);

      else
        r = MRSS_ERR_VERSION;

      free(c);
    }

    else
      r = MRSS_ERR_VERSION;
  }

  else if (!strcmp(cur->value, "RDF"))
    r = __mrss_parser_rss(MRSS_VERSION_1_0, doc, cur->children, ret);

  else if (!strcmp(cur->value, "feed"))
    r = __mrss_parser_atom(doc, cur, ret);

  else
    r = MRSS_ERR_PARSER;

  return r;
}

/*************************** EXTERNAL FUNCTION ******************************/

mrss_error_t mrss_parse_url(char *url, mrss_t **ret) {
  return mrss_parse_url_with_options_error_and_transfer_buffer(
      url, ret, NULL, NULL, NULL, NULL);
}

mrss_error_t mrss_parse_url_with_options(char *url, mrss_t **ret,
                                         mrss_options_t *options) {
  return mrss_parse_url_with_options_error_and_transfer_buffer(
      url, ret, options, NULL, NULL, NULL);
}

mrss_error_t mrss_parse_url_with_options_and_error(char *url, mrss_t **ret,
                                                   mrss_options_t *options,
                                                   CURLcode *code) {
  return mrss_parse_url_with_options_error_and_transfer_buffer(
      url, ret, options, code, NULL, NULL);
}

mrss_error_t mrss_parse_url_with_options_error_and_transfer_buffer(
    char *url, mrss_t **ret, mrss_options_t *options, CURLcode *code,
    char **feed_content, int *feed_size) {
  nxml_t *doc;
  mrss_error_t err;
  char *buffer;
  size_t size;

  if (feed_content)
    *feed_content = NULL;

  if (feed_size)
    *feed_size = 0;

  if (!url || !ret)
    return MRSS_ERR_DATA;

  if (nxml_new(&doc) != NXML_OK)
    return MRSS_ERR_POSIX;

  if (options) {
    if (options->timeout >= 0)
      nxml_set_timeout(doc, options->timeout);

    if (options->proxy)
      nxml_set_proxy(doc, options->proxy, options->proxy_authentication);

    if (options->authentication)
      nxml_set_authentication(doc, options->authentication);

    if (options->user_agent)
      nxml_set_user_agent(doc, options->user_agent);

    nxml_set_certificate(doc, options->certfile, options->password,
                         options->cacert, options->verifypeer);
  }

  if (!(buffer = __mrss_download_file(doc, url, &size, &err, code)))
    return err;

  if (nxml_parse_buffer(doc, buffer, size) != NXML_OK) {
    free(buffer);
    nxml_free(doc);

    return MRSS_ERR_PARSER;
  }

  if (!(err = __mrss_parser(doc, ret))) {
    if (!((*ret)->file = strdup(url))) {
      mrss_free(*ret);
      nxml_free(doc);
      free(buffer);

      return MRSS_ERR_POSIX;
    }

    (*ret)->size = size;
  }

  nxml_free(doc);

  /* transfer ownership: */
  if (!feed_content)
    free(buffer);
  else
    *feed_content = buffer;

  if (feed_size)
    *feed_size = size;

  return err;
}

mrss_error_t mrss_parse_file(char *file, mrss_t **ret) {
  nxml_t *doc;
  mrss_error_t err;
  struct stat st;

  if (!file || !ret)
    return MRSS_ERR_DATA;

  if (lstat(file, &st))
    return MRSS_ERR_POSIX;

  if (nxml_new(&doc) != NXML_OK)
    return MRSS_ERR_POSIX;

  if (nxml_parse_file(doc, file) != NXML_OK) {
    nxml_free(doc);
    return MRSS_ERR_PARSER;
  }

  if (!(err = __mrss_parser(doc, ret))) {
    if (!((*ret)->file = strdup(file))) {
      nxml_free(doc);
      mrss_free(*ret);

      return MRSS_ERR_POSIX;
    }

    (*ret)->size = st.st_size;
  }

  nxml_free(doc);

  return err;
}

mrss_error_t mrss_parse_buffer(char *buffer, size_t size, mrss_t **ret) {
  nxml_t *doc;
  mrss_error_t err;

  if (!buffer || !size || !ret)
    return MRSS_ERR_DATA;

  if (nxml_new(&doc) != NXML_OK)
    return MRSS_ERR_POSIX;

  if (nxml_parse_buffer(doc, buffer, size)) {
    nxml_free(doc);
    return MRSS_ERR_PARSER;
  }

  if (!(err = __mrss_parser(doc, ret)))
    (*ret)->size = size;

  nxml_free(doc);
  return err;
}

/* EOF */
