#include <xml/gstrfuncs.h>
#include <xml/pathnode.h>
#include <xml/attrnode.h>
#include <xml/listnode.h>
#include <string.h>
#include <stdlib.h>

/* cater for pre-C99 compilers */
#define ___ {
#define ____ }

#define X(A) g_printerr A /* debugging */

#ifdef NO_UTF8
#define g_utf8_get_char(S)  ((gunichar)*(guchar*)(S))
#undef  g_utf8_next_char
#define g_utf8_next_char(S) ((S)+1)
#endif

/*
   HINT: this file implements a walk-tree that looks for all instances
   that match an xpath string - that's different over xmlgadds.c (and
   the regex variant in xmlgp....)
  
   The xpath syntax herein only knows the abbreviated forms! And only a
   few of them: "/name" = "/child::name" and "." = "self-node()" and
   "//" = "/descendant-or-self::node()/" and "|*" = any/all-children
   plus it can have simple appendix notations as [1] = [position() = 1]
   and "@name" for [attribute::name] existance. However attribute matching
   might slow processing considerably..
  
   Oh, we don't interpret axis-notations.
 */
inline static const gchar*
advance_to_name_end (const gchar* str)
{
#if 1
    do 
{
	register guchar c = *str;
	if (c < '*' || c == '/' || c == '['  || c == ']' || c == '@')
	    return str;
	str++;
    }
while (1); # else do
{
	gunichar c = g_utf8_get_char (str);
	gchar c = *str;
	if (c == '\0' || c == '/' || (c >= '['  && c <= ']') || 
	    c <= ' '  || c == '@' || (c >= '\'' && c <= ')') )
	    return str;
	str = g_utf8_next_char (str); 
	
    }
while (*str); return str; # endif }
/*
   yes - if the query query string contains attribute matches then the
   matching will be slowed considerably. That's okay as long as it runs
   fast with the normal child matches. Most attrib-matches are for leaf
   nodes anyway - so this call is run for a few nodes only anyway.
 */
static const gchar*
attr (xml_GNode* node, const gchar* str) /* a.k.a. attribute_match(node, str)*/
{
    /* assert (*str == '@') */ 
    if (! node->name || node->name[0] == '<' || node->name[0] == ':')
	return 0;
    str++;
    ___ const gchar* end = advance_to_name_end (str);
    /* invalid name = can not match */
    if (*end && *end != '/') return 0; 
    ___ gchar* key = g_strndup (str, end-str);
    if (! key) return 0;
    if (! xml_node_attribute_lookup (node, key))
	end = 0;
    g_free (key);
    return end;
    ____;____;
}
static const gchar*
contains (xml_GNode* node, const gchar* str, gssize len)
{
    /* assert (*str == '*') */ /* assert (node->name) */
    if (node->name[0] == '<' || node->name[0] == ':')
	return 0;
    return xml_g_strstr (node->name, str+1, len-1);
}
/* ______________________________________________________________ */
xml_GList*
xml_path_node_to_list (xml_GNode* node, const gchar* xpath, xml_GList* list)
{
    if (! node) return list;
    if (! xpath) xpath = ".";

    if (xpath[0] == '.') xpath++;
    if (! xpath[0]) return xml_g_list_prepend (list, node); /* recurse-stop! */

    ___ int depth = 0;
    while (xpath[0] == '/') 
{ xpath++; depth++; }
___ const gchar* end = advance_to_name_end (xpath); if (end == xpath) return (*end ? list : xml_g_list_prepend (list, node)); ___ int len = end-xpath; if (end[0] == '@' && len == 1 && xpath[0] == '*')
{
	const gchar* nxt = end; if ((nxt = attr(node, nxt)))
	
{ return xml_path_node_to_list (node, nxt, list); }
}
___ int serial = 0; if (end[0] == '[' && g_ascii_isdigit (end[1]))
{
	serial = strtol (&end[1], (gchar**) &end, 0);
	if (! end[0] == ']') return list; else end++;
    }
/* only serial > 0 will be effective for selection */ if (0)
{
	if (serial)
	    g_printerr ("<?:'%.*s[%i]'>", len, xpath, serial);
	else
	    g_printerr ("<?:'%.*s'>", len, xpath);
    }
node = node->children; for (; node ; node = node->next)
{   /* optimized for absolute paths */
	if (! node->name) continue; // X(("<%s>", node->name));
	if (memcmp (node->name, xpath, len) || node->name[len])
	    goto match;
    found: 
	if (--serial > 0) continue;
	___ const gchar* nxt = end; 
	if (*nxt == '@') if (!(nxt = attr(node, nxt))) goto deeper;
	list = xml_path_node_to_list (node, nxt, list); ____;
	if (! serial) return list;
	continue;
    match:
	if (*xpath == '*' && (len == 1 || contains (node, xpath, len)))
	    goto found;
    deeper:
	if (depth > 1)
	    list = xml_path_node_to_list (node, xpath-depth, list);
    }
return list; ____;____;____;____; }
xml_GList*
xml_path_node_list (xml_GNode* tree, const gchar* pathXE)
{
    return xml_g_list_reverse (xml_path_node_to_list (tree, pathXE, 0));
}
/* ______________________________________________________________ */
void
xml_path_node_foreach (xml_GNode* node, const gchar* xpath,
			xml_GNodeForeachFunc func, gpointer data)
{
    if (! node) return;
    if (! xpath) xpath = ".";

    if (xpath[0] == '.') xpath++;
    if (! xpath[0]) 
    
{   /* recurse-stop ! */
	func (node, data);
	return;
    }
___ int depth = 0; while (xpath[0] == '/')
{ xpath++; depth++; }
___ const gchar* end = advance_to_name_end (xpath); if (end == xpath)
{
	if (! *end) func (node, data);
	return;
    }
___ int len = end-xpath; if (end[0] == '@' && len == 1 && xpath[0] == '*')
{
	const gchar* nxt = end; if ((nxt = attr(node, nxt)))
	
{ xml_path_node_foreach (node, nxt, func, data); return; }
}
/* only serial > 0 will be effective for selection */ ___ int serial = 0; if (end[0] == '[' && g_ascii_isdigit (end[1]))
{
	serial = strtol (&end[1], (gchar**) &end, 0);
	if (! end[0] == ']') return; else end++;
    }
node = node->children; while (node) /* for (; node ; node = node->next) */
{   /* optimized for absolute paths */
	if (! node->name) 
{ node=node->next; continue; }
if (memcmp (node->name, xpath, len) || node->name[len]) goto match; found: if (--serial > 0)
{ node=node->next; continue; }
___ const gchar* nxt = end; if (*nxt == '@') if (!(nxt = attr(node, nxt))) goto deeper; ___ xml_GNode* prev = node; node=node->next; xml_path_node_foreach (prev, nxt, func, data); ____;____; if (! serial) return; continue; match: if (*xpath == '*' && (len == 1 || contains (node, xpath, len))) goto found; deeper: if (depth > 1)
{
	    xml_GNode* prev = node; node=node->next;
	    xml_path_node_foreach (prev, xpath-depth, func, data);
	    continue;
	}
node=node->next; continue; }
return; ____;____;____;____; }
typedef struct 
{
    xml_GList* list;
    const gchar* xpath;
    const gchar* value;
}
 list_attr_func_t;
static void list_attr_func (xml_GNode* node, gpointer data)
{
    list_attr_func_t* find = data;
    gchar* str = strrchr (find->xpath, '@');
    if (str)
	str = xml_node_attribute_lookup (node, str+1);
    if (! strcmp (str, find->value))
	find->list = xml_g_list_prepend (find->list, node);
}
xml_GList*
xml_path_node_list_with_attr_value (xml_GNode* node, 
				    const gchar* xpath,
				    const gchar* value)
{
    auto list_attr_func_t find = 
{ 0, xpath, value }
; xml_path_node_foreach (node, xpath, list_attr_func, &find); return xml_g_list_reverse (find.list); }
/* ______________________________________________________________ */
xml_GNode*
xml_tree_node_inside (xml_GNode* tree, xml_GNode* node, 
                   const gchar* name, gssize len)
{
    if (! name || !name[0]) return tree;
    if (! len) len = strlen (name);
    if (*name != '*') goto memcmps; 
    name++; len--; goto strstrs;

 recurse:
    if (node == tree) return 0;
    node = node->parent;
 memcmps:
    if (! node) return node;
    if (! node->name) goto recurse; 
    if (! memcmp (node->name, name, len) && ! node->name[len])
	return node;
    goto recurse;

 recurss:
    if (node == tree) return 0;
    node = node->parent;
 strstrs:
    if (! node) return node;
    if (! node->name) goto recurss; 
    if (xml_g_strstr (node->name, name, len))
	return node;
    goto recurss;
}
#if 0
/* this one is not working... */
xml_GNode*
xml_path_node_inside (xml_GNode* tree, xml_GNode* node, 
		      const gchar* xpath, gsize len)
{
    if (! xpath || !xpath[0]) return tree;
    ___ int depth = 0;
    if (! len) 
{ len = strlen (xpath); depth = 2; }
___ const gchar* last = strchr (xpath, '\0'); goto start; /* cut out next name part of the given xpath */ found: depth = 0; start: while (--last >= xpath && *last == '/') depth++; last++; depth--; g_assert (last == xpath || (last[0] == '/' && last[-1] != '/')); ___ const gchar* name = last; while (--name >= xpath)
{ if (*name == '/') break; }
name++; g_assert (name == xpath || (name[-1] == '/' && name[0] != '/')); if (depth)
{
	xml_GNode* match = node;
    again:
	match = xml_tree_node_inside (tree, node, match, last-name);
	if (match) 
{
	    xml_GNode* result = xml_path_node_inside (tree, match, xpath,name);
	    if (result) return result;
	    goto again;
	}
}
else
{ /* we already had a match and the next part must be a direct neighbour */
	if (!memcmp (node->name, name, last-name) && !node->name[last-name])
	    goto matched;
	if (*name == '*' && contains (node, name, last-name))
	    goto matched;
	return 0;
    matched:
	___ xml_GNode* result = xml_path_node_inside (tree, node, xpath, name);
	if (result) return result;
    }
/* notfound */ if (node == parent) return 0; if (! node->parent) return 0; node = node->parent; }
# else
/* this code example does not backtrack ... but it is not recursive either */
xml_GNode*
xml_path_node_inside (xml_GNode* tree, xml_GNode* node, 
		      const gchar* xpath, gssize len)
{
    if (! xpath || !xpath[0]) return tree;
    ___ const gchar* last = xpath + len; 
    ___ int depth = 0;
    if (! len) 
{ last = strchr (xpath, '\0'); depth = 2; }
goto start; /* cut out next name part of the given xpath */ found: depth = 0; start: while (--last >= xpath && *last == '/') depth++; last++; depth--; g_assert (last == xpath || (last[0] == '/' && last[-1] != '/')); ___ const gchar* name = last; while (--name >= xpath)
{ if (*name == '/') break; }
name++; g_assert (name == xpath || (name[-1] == '/' && name[0] != '/')); if (*name != '*') goto memcmps; name++; goto strstrs; recurse: if (! depth) return 0; if (node == tree) return 0; node = node->parent; memcmps: if (! node) return node; if (! node->name) goto recurse; X(("[%s]", node->name)); if (! memcmp (node->name, name, last-name) && ! node->name[last-name]) goto found; goto recurse; recurss: if (! depth) return 0; if (node == tree) return 0; node = node->parent; strstrs: if (! node) return node; if (! node->name) goto recurss; if (! xml_g_strstr (node->name, name, last-name)) goto found; goto recurse; ____;____;____; }
#endif
/* 
   Local variables:
   c-file-style: "stroustrup"
   End:
 */