/* nXml - Copyright (C) 2005-2021 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 "nxml.h"

struct __nxml_data_ns_t {
  nxml_namespace_t *ns;
  struct __nxml_data_ns_t *next;
};

static void __nxml_namespace_free_item(nxml_data_t *e) {
  nxml_namespace_t *ns;
  nxml_data_t *child;

  while (e->ns_list) {
    ns = e->ns_list->next;

    if (e->ns_list->prefix)
      free(e->ns_list->prefix);

    if (e->ns_list->ns)
      free(e->ns_list->ns);

    free(e->ns_list);

    e->ns_list = ns;
  }

  e->ns = NULL;

  child = e->children;
  while (child) {
    __nxml_namespace_free_item(child);
    child = child->next;
  }
}

static void __nxml_namespace_free(nxml_t *nxml) {
  nxml_data_t *e;

  e = nxml->data;
  while (e) {
    __nxml_namespace_free_item(e);

    e = e->next;
  }
}

int __nxml_namespace_parse_add(nxml_data_t *data, char *prefix,
                               char *namespace) {
  nxml_namespace_t *ns;

  if (!(ns = (nxml_namespace_t *)calloc(1, sizeof(nxml_namespace_t))))
    return 1;

  if (prefix && !(ns->prefix = strdup(prefix))) {
    free(ns);
    return 1;
  }

  if (!(ns->ns = strdup(namespace))) {
    if (ns->prefix)
      free(ns->prefix);
    free(ns);
    return 1;
  }

  ns->next = data->ns_list;
  data->ns_list = ns;

  return 0;
}

static int __nxml_namespace_find_item(nxml_t *nxml, nxml_data_t *e) {
  nxml_data_t *child;
  nxml_attr_t *att;

  att = e->attributes;
  while (att) {
    if (!strcmp(att->name, "xmlns")) {
      if (__nxml_namespace_parse_add(e, NULL, att->value)) {
        __nxml_namespace_free(nxml);
        return 1;
      }
    } else if (!strncmp(att->name, "xmlns:", 6)) {
      if (__nxml_namespace_parse_add(e, att->name + 6, att->value)) {
        __nxml_namespace_free(nxml);
        return 1;
      }
    }

    att = att->next;
  }

  child = e->children;
  while (child) {
    if (child->type == NXML_TYPE_ELEMENT)
      __nxml_namespace_find_item(nxml, child);
    child = child->next;
  }

  return 0;
}

static int __nxml_namespace_find(nxml_t *nxml) {
  nxml_data_t *e;

  e = nxml->data;
  while (e) {
    if (e->type == NXML_TYPE_ELEMENT)
      __nxml_namespace_find_item(nxml, e);
    e = e->next;
  }

  return 0;
}

static void __nxml_namespace_associate_attribute(struct __nxml_data_ns_t *list,
                                                 nxml_attr_t *e) {
  int i;
  int len = strlen(e->name);
  int k;

  for (i = k = 0; i < len; i++)
    if (e->name[i] == ':') {
      k = i;
      break;
    }

  if (!k) {
    while (list) {
      if (!list->ns->prefix) {
        e->ns = list->ns;
        return;
      }
      list = list->next;
    }

    return;
  }

  else {
    while (list) {
      if (list->ns->prefix && !strncmp(list->ns->prefix, e->name, k)) {
        char *a = strdup(e->name + strlen(list->ns->prefix) + 1);

        if (!a)
          return;

        free(e->name);
        e->name = a;

        e->ns = list->ns;
        return;
      }
      list = list->next;
    }
  }
}

static void __nxml_namespace_associate_item(struct __nxml_data_ns_t *list,
                                            nxml_data_t *e) {
  int i;
  int len;
  int k;
  nxml_attr_t *attr;

  attr = e->attributes;
  while (attr) {
    __nxml_namespace_associate_attribute(list, attr);
    attr = attr->next;
  }

  len = strlen(e->value);

  for (i = k = 0; i < len; i++)
    if (e->value[i] == ':') {
      k = i;
      break;
    }

  if (!k) {
    while (list) {
      if (!list->ns->prefix) {
        e->ns = list->ns;
        return;
      }
      list = list->next;
    }

    return;
  }

  else {
    while (list) {
      if (list->ns->prefix && !strncmp(list->ns->prefix, e->value, k)) {
        char *a = strdup(e->value + strlen(list->ns->prefix) + 1);

        if (!a)
          return;

        free(e->value);
        e->value = a;

        e->ns = list->ns;
        return;
      }
      list = list->next;
    }
  }
}

static void __nxml_namespace_associate(struct __nxml_data_ns_t **list,
                                       nxml_data_t *root) {
  nxml_data_t *e;
  nxml_namespace_t *ns;
  struct __nxml_data_ns_t *tmp, *old;

  ns = root->ns_list;
  while (ns) {
    if (!(tmp = calloc(1, sizeof(struct __nxml_data_ns_t))))
      return;

    tmp->ns = ns;
    tmp->next = (*list);
    (*list) = tmp;

    ns = ns->next;
  }

  __nxml_namespace_associate_item(*list, root);

  e = root->children;
  while (e) {
    if (e->type == NXML_TYPE_ELEMENT)
      __nxml_namespace_associate(list, e);

    e = e->next;
  }

  ns = root->ns_list;
  while (ns) {
    tmp = *list;
    old = NULL;

    while (tmp) {
      if (tmp->ns == ns) {
        if (old)
          old->next = tmp->next;
        else
          *list = tmp->next;

        free(tmp);
        break;
      }

      old = tmp;
      tmp = tmp->next;
    }

    ns = ns->next;
  }
}

static void __nxml_namespace_connect(nxml_t *nxml) {
  nxml_data_t *e;
  struct __nxml_data_ns_t *list = NULL;

  e = nxml->data;
  while (e) {
    if (e->type == NXML_TYPE_ELEMENT)
      __nxml_namespace_associate(&list, e);

    e = e->next;
  }
}

void __nxml_namespace_parse(nxml_t *nxml) {
  __nxml_namespace_free(nxml);

  if (__nxml_namespace_find(nxml))
    return;

  __nxml_namespace_connect(nxml);
}

/* EOF */
