/* Snownews - A lightweight console RSS newsreader
 * 
 * Copyright 2003-2004 Rene Puls <rpuls@gmx.net> and
 *                     Oliver Feiler <kiza@kcore.de>
 * Written for Snownews.
 *
 * http://kiza.kcore.de/software/snownews/
 * http://home.kcore.de/~kianga/study/c/xmlparse.c
 *
 * xmlparse.c
 *
 * Please read the file README.patching before changing any code in this file!
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
 

#include <string.h>

#include "snownews_config.h"
#include "xmlparse.h"
/* #include "conversions.h" */

int saverestore;
struct newsitem *copy;
struct newsitem *firstcopy;

/* Wird während des Parsens aufgerufen, falls wir auf ein <channel> Element
   treffen. Die Funktion gibt ein neues Struct für den Newsfeed zurück. */

void parse_rdf10_channel(struct feed * feed, xmlDocPtr doc, xmlNodePtr node) {
	xmlNodePtr cur;
	

	/* Free everything before we write to it again. */
	
	if (feed == NULL)
	  return;
	
	if (feed->title)
	  free (feed->title);
	
	if (feed->link)
	  free (feed->link);
	
	if (feed->description)
	  free (feed->description);
	
	if (feed->items != NULL) {
	  while (feed->items->next_ptr != NULL) {
	    feed->items = feed->items->next_ptr;
	    
	    if (feed->items->prev_ptr->title)
	      free (feed->items->prev_ptr->title);
	    
	    if (feed->items->prev_ptr->link)
	      free (feed->items->prev_ptr->link);
	    
	    if (feed->items->prev_ptr->description)
	      free (feed->items->prev_ptr->description);
	    
	    if (feed->items->prev_ptr)
	      free (feed->items->prev_ptr);
	  }
	  
	  if (feed->items->title)
	    free (feed->items->title);
	  
	  if (feed->items->link)
	    free (feed->items->link);
	  
	  if (feed->items->description)
	    free (feed->items->description);
	  
	  if (feed->items)
	    free (feed->items);
	}
	
	/* Im Augenblick haben wir noch keine Items, also die Liste auf NULL setzen. */
	feed->feedurl = NULL;
	feed->feed = NULL;
	feed->title = NULL;
	feed->link= NULL;
	feed->description = NULL;
	feed->lastmodified = NULL;
	feed->lasthttpstatus = 200;
	feed->cookies = NULL;
	feed->authinfo = NULL;
	feed->servauth = NULL;
	feed->items = NULL;
	feed->next_ptr = NULL;
	feed->prev_ptr = NULL;
	feed->problem = 0;
	feed->override = NULL;
	feed->original = NULL;
	feed->perfeedfilter = NULL;
	feed->execurl = 0;
	
	/* Alle Tags im <channel> Tag durchgehen und die Informationen extrahieren */
	for (cur = node; cur != NULL; cur = cur->next) {
		if (cur->type != XML_ELEMENT_NODE)
			continue;
		if (xmlStrcmp(cur->name, "title") == 0) {
			feed->title = xmlNodeListGetString(doc, cur->children, 1);
			/* CleanupString (feed->title); */
			/* Remove trailing newline */
			if (feed->title != NULL) {
				if (feed->title[strlen(feed->title)-1] == '\n')
					feed->title[strlen(feed->title)-1] = '\0';
			}
		}
		else if (xmlStrcmp(cur->name, "link") == 0) {
			feed->link = xmlNodeListGetString(doc, cur->children, 1);
			/* Remove trailing newline */
			if (feed->link != NULL) {
				if (feed->link[strlen(feed->link)-1] == '\n')
					feed->link[strlen(feed->link)-1] = '\0';
			}
		}
		else if (xmlStrcmp(cur->name, "description") == 0) {
			feed->description = xmlNodeListGetString(doc, cur->children, 1);
			/* CleanupString (feed->description); */
		}
	}
}


void parse_rdf20_channel(struct feed * feed, xmlDocPtr doc, xmlNodePtr node)
{
	xmlNodePtr cur;
	
	/* Free everything before we write to it again. */
	if (feed->title)
	  free (feed->title);
	
	if (feed->link)
	  free (feed->link);
	
	if (feed->description)
	  free (feed->description);
	
	if (feed->items != NULL) {
	  while (feed->items->next_ptr != NULL) {
	    feed->items = feed->items->next_ptr;
	    
	    if (feed->items->prev_ptr->title)
	      free (feed->items->prev_ptr->title);
	    
	    if (feed->items->prev_ptr->link)
	      free (feed->items->prev_ptr->link);
	    
	    if (feed->items->prev_ptr->description)
	      free (feed->items->prev_ptr->description);
	    
	    if (feed->items->prev_ptr)
	      free (feed->items->prev_ptr);
	  }
	  
	  if (feed->items->title)
	    free (feed->items->title);
	  
	  if (feed->items->link)
	    free (feed->items->link);
	  
	  if (feed->items->description)
	    free (feed->items->description);
	  
	  if (feed->items)
	    free (feed->items);
	}
	
	/* Im Augenblick haben wir noch keine Items, also die Liste auf NULL setzen. */
	feed->items = NULL;
	feed->title = NULL;
	feed->link = NULL;
	feed->description = NULL;
	
	/* Alle Tags im <channel> Tag durchgehen und die Informationen extrahieren */
	for (cur = node; cur != NULL; cur = cur->next) {
		if (cur->type != XML_ELEMENT_NODE)
			continue;
		if (xmlStrcmp(cur->name, "title") == 0) {
			feed->title = xmlNodeListGetString(doc, cur->children, 1);
			/* CleanupString (feed->title); */
			/* Remove trailing newline */
			if (feed->title != NULL) {
				if (feed->title[strlen(feed->title)-1] == '\n')
					feed->title[strlen(feed->title)-1] = '\0';
			}
		}
		else if (xmlStrcmp(cur->name, "link") == 0) {
			feed->link = xmlNodeListGetString(doc, cur->children, 1);
			/* Remove trailing newline */
			if (feed->link != NULL) {
				if (feed->link[strlen(feed->link)-1] == '\n')
					feed->link[strlen(feed->link)-1] = '\0';
			}
		}
		else if (xmlStrcmp(cur->name, "description") == 0) {
			feed->description = xmlNodeListGetString(doc, cur->children, 1);
			/* CleanupString (feed->description); */
		} else if (xmlStrcmp(cur->name, "item") == 0) {
			parse_rdf10_item(feed, doc, cur->children);
		}
	}
}

/* Diese Funktion wird jedes Mal aufgerufen, wenn wir auf ein <item> treffen.
   Als Parameter benötigt sie den aktuellen Newsfeed (struct newsfeed *),
   sowie das aktuelle XML Document-Handle und das aktuelle Element, beide
   kommen direkt von der libxml. */

void parse_rdf10_item(struct feed *feed, xmlDocPtr doc, xmlNodePtr node) 
{
	xmlNodePtr cur;
	xmlChar *readstatusstring;

	struct newsitem *item;
	struct newsitem *current;
	
	/* Speicher für ein neues Newsitem reservieren */
	item = malloc(sizeof (struct newsitem));
	
	item->title = NULL;
	item->link = NULL;
	item->description = NULL;
	
	item->readstatus = 0;
		
	/* Alle Tags im <item> Tag durchgehen und die Informationen extrahieren.
	   Selbe Vorgehensweise wie in der parse_channel() Funktion */
	for (cur = node; cur != NULL; cur = cur->next) {
		if (cur->type != XML_ELEMENT_NODE)
			continue;
		if (xmlStrcmp(cur->name, "title") == 0) {
			item->title = xmlNodeListGetString(doc, cur->children, 1);
			/* CleanupString (item->title); */
			/* Remove trailing newline */
			if (item->title != NULL) {
				if (item->title[strlen(item->title)-1] == '\n')
					item->title[strlen(item->title)-1] = '\0';
			}
		}
		else if (xmlStrcmp(cur->name, "link") == 0) {
			item->link = xmlNodeListGetString(doc, cur->children, 1);
			if (item->link == NULL) {
				if (xmlStrcmp(cur->name, "guid") == 0)
					item->link = xmlNodeListGetString(doc, cur->children, 1);
			}
			/* Remove trailing newline */
			if (item->link != NULL) {
				if (item->link[strlen(item->link)-1] == '\n')
					item->link[strlen(item->link)-1] = '\0';
			}
		}
		else if (xmlStrcmp(cur->name, "description") == 0) {
			item->description = xmlNodeListGetString(doc, cur->children, 1);
			/* CleanupString (item->description); */
		}
		else if (xmlStrcmp(cur->name, "readstatus") == 0) {
			/* Will cause memory leak otherwise, xmlNodeListGetString must be freed. */
			readstatusstring = xmlNodeListGetString(doc, cur->children, 1);
			item->readstatus = atoi (readstatusstring);
			xmlFree (readstatusstring);
		}
	}
	
	/* If saverestore == 1, restore readstatus. */
	if (saverestore == 1) {
		for (current = firstcopy; current != NULL; current = current->next_ptr) {
			if ((current->link != NULL) && (item->link != NULL)) {
				if (strcmp(item->link, current->link) == 0)
					item->readstatus = current->readstatus;
			}
		}
	}
		
	item->next_ptr = NULL;
	if (feed->items == NULL) {
		item->prev_ptr = NULL;
		feed->items = item;
	} else {
		item->prev_ptr = feed->items;
		while (item->prev_ptr->next_ptr != NULL)
			item->prev_ptr = item->prev_ptr->next_ptr;
		item->prev_ptr->next_ptr = item;
	}
}


/* rrr */

int DeXML (struct feed * cur_ptr) {
	xmlDocPtr doc;
	xmlNodePtr cur;
	struct newsitem *cur_item;
		
	saverestore = 0;
	/* Wenn cur_ptr->items != NULL dann können wir uns item->readstatus
	   zwischenspeichern. */
	if (cur_ptr->items != NULL) {
		saverestore = 1;
	
		firstcopy = NULL;
		
		/* Copy current newsitem struct. */	
		for (cur_item = cur_ptr->items; cur_item != NULL; cur_item = cur_item->next_ptr) {
			copy = malloc (sizeof(struct newsitem));
			copy->title = NULL;
			copy->description = NULL;
			copy->readstatus = cur_item->readstatus;
			if (cur_item->link != NULL)
				copy->link = strdup (cur_item->link);
			else
				copy->link = NULL;
			
			copy->next_ptr = NULL;
			if (firstcopy == NULL) {
				copy->prev_ptr = NULL;
				firstcopy = copy;
			} else {
				copy->prev_ptr = firstcopy;
				while (copy->prev_ptr->next_ptr != NULL)
					copy->prev_ptr = copy->prev_ptr->next_ptr;
				copy->prev_ptr->next_ptr = copy;
			}
		}
	}
	
	/* xmlRecoverMemory:
	   parse an XML in-memory document and build a tree.
       In case the document is not Well Formed, a tree is built anyway. */
	doc = xmlRecoverMemory(cur_ptr->feed, strlen(cur_ptr->feed));
	
	if (doc == NULL)
		return 2;
	
	/* Das Root-Element finden (in unserem Fall sollte es "<RDF:RDF>" heißen.
	   Dabei wird das RDF: Prefix fürs Erste ignoriert, bis der Jaguar
	   herausfindet, wie man das genau auslesen kann (jau). */
	cur = xmlDocGetRootElement(doc);
	
	if (cur == NULL) {
	  xmlFreeDoc (doc);
	  return 2;
	}
	

	/* Überprüfen, ob das Element auch wirklich <RDF> heißt */
	if (xmlStrcmp(cur->name, "RDF") == 0) {
	
		/* Jetzt gehen wir alle Elemente im Dokument durch. Diese Schleife
		   selbst läuft jedoch nur durch die Elemente auf höchster Ebene
		   (bei HTML wären das nur HEAD und BODY), wandert also nicht die 
		   gesamte Struktur nach unten durch. Dafür sind die Funktionen zuständig, 
		   die wir dann in der Schleife selbst aufrufen. */
		for (cur = cur->children; cur != NULL; cur = cur->next) {
			if (cur->type != XML_ELEMENT_NODE)
				continue;
			if (xmlStrcmp(cur->name, "channel") == 0)
				parse_rdf10_channel(cur_ptr, doc, cur->children);
			if (xmlStrcmp(cur->name, "item") == 0)
				parse_rdf10_item(cur_ptr, doc, cur->children);
			/* Last-Modified is only used when reading from internal feeds (disk cache). */
			if (xmlStrcmp(cur->name, "lastmodified") == 0)
				cur_ptr->lastmodified = xmlNodeListGetString(doc, cur->children, 1);
		}
	} else if (xmlStrcmp(cur->name, "rss") == 0) {
		for (cur = cur->children; cur != NULL; cur = cur->next) {
			if (cur->type != XML_ELEMENT_NODE)
				continue;
			if (xmlStrcmp(cur->name, "channel") == 0)
				parse_rdf20_channel(cur_ptr, doc, cur->children);
		}
	} else {
	  xmlFreeDoc(doc);
	  return 3;
	}
	

	xmlFreeDoc(doc);
	
	if (saverestore == 1) {
		/* free struct newsitem *copy. */
		while (firstcopy->next_ptr != NULL) {
			firstcopy = firstcopy->next_ptr;
			
			if (firstcopy->prev_ptr->link)
			  free (firstcopy->prev_ptr->link);
			
			if (firstcopy->prev_ptr)
			  free (firstcopy->prev_ptr);
		}
		if (firstcopy->link)
		  free (firstcopy->link);
		
		if (firstcopy)
		  free (firstcopy);
		
		firstcopy = NULL;
	}
	
	if (cur_ptr->original != NULL)
		free (cur_ptr->original);

	/* Set -> title to something if it's a NULL pointer to avoid crash with strdup below. */
	if (cur_ptr->title == NULL)
		cur_ptr->title = strdup (cur_ptr->feedurl);
	cur_ptr->original = strdup (cur_ptr->title);
	
	/* Restore custom title. */
	if (cur_ptr->override != NULL) {
	  if (cur_ptr->title)
	    free (cur_ptr->title);
	  cur_ptr->title = strdup (cur_ptr->override);
	}
		
	return 0;
}


void print_feed ( struct feed * f )
{
  struct newsitem * item;
  
  if (!f)
    {
      fprintf(stderr, "Can't print the NULL feed. ;-)\n");
      exit (-1);
    }
  
  if (DeXML(f))
    {
      printf ("The feed could not be parsed.");
      return;
    }
  
  item = f->items;
  
  printf ("---[[ %s ]]\n", f->title);
  while ( item )
    {
      /* printf ("---\n");
      printf ("TITLE: %s\n", item->title);
      printf ("LINK : %s\n", item->link);
      printf ("DESC : %s\n", item->description); */
      
      printf ("[ %s ]\n", item->title);
      if (item->description)
	printf ("%s\n\n", item->description);
      item = item->next_ptr;
    }
}


struct feed * new_feed ( char * rawxml )
{
  struct feed * f;
  
  f = ( struct feed* ) malloc ( sizeof ( struct feed ) );
  
  f->feedurl = NULL;
  f->feed    = rawxml;
  f->title   = NULL;
  f->link    = NULL;
  f->description = NULL;
  f->lastmodified = NULL;
  f->lasthttpstatus = 200;
  f->cookies = NULL;
  f->authinfo = NULL;
  f->servauth = NULL;
  
  f->items = NULL;
  
  f->next_ptr = NULL;
  f->prev_ptr = NULL;
  
  f->problem = (int) NULL; /* null problemo :-P */
  f->override = NULL;
  f->original = NULL;
  f->perfeedfilter = NULL;
  f->execurl = 0; /* ? */
  
  return f;
}


