mash/beerxml.c

changeset 103
99c47a8a61cb
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mash/beerxml.c	Sat Jul 12 21:59:19 2014 +0200
@@ -0,0 +1,1531 @@
+/*****************************************************************************
+ * Copyright (C) 2014
+ *   
+ * Michiel Broek <mbroek at mbse dot eu>
+ *
+ * This file is part of the mbsePi-apps
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * mbsePi-apps 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 EC-65K; see the file COPYING.  If not, write to the Free
+ * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *****************************************************************************/
+
+#include "mash.h"
+#include "beerxml.h"
+
+
+recipe_rec		*recipes = NULL;
+
+
+int parseStyle(xmlDocPtr, xmlNodePtr, style_rec **);
+int parseEquipment(xmlDocPtr, xmlNodePtr, equipment_rec **);
+int parseMashStep(xmlDocPtr, xmlNodePtr, mash_step **);
+int parseMashSteps(xmlDocPtr, xmlNodePtr, mash_step **);
+int parseMash(xmlDocPtr, xmlNodePtr, mash_profile **);
+int parseFermentable(xmlDocPtr, xmlNodePtr, fermentable_rec **);
+int parseFermentables(xmlDocPtr, xmlNodePtr, fermentable_rec **);
+
+
+
+
+/*
+ * Parse style
+ */
+int parseStyle(xmlDocPtr doc, xmlNodePtr cur, style_rec **style)
+{
+    xmlChar     *key;
+    style_rec   *item;
+    float       val;
+
+    item = (style_rec *)malloc(sizeof(style_rec));
+    item->name = item->category = item->category_number = item->style_letter = item-> style_guide =
+	    item->type = item->notes = item->profile = item->ingredients = item->examples = NULL;
+    item->og_min = item->og_max = item->fg_min = item->fg_max = item->ibu_min = item->ibu_max =
+	    item->color_min = item->color_max = item->carb_min = item->carb_max = 
+	    item->abv_min = item->abv_max = 0.0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CATEGORY"))) {
+	    item->category = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CATEGORY_NUMBER"))) {
+	    item->category_number = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STYLE_LETTER"))) {
+	    item->style_letter = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STYLE_GUIDE"))) {
+	    item->style_guide = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"OG_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->og_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"OG_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->og_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FG_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->fg_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FG_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->fg_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"IBU_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->ibu_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"IBU_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->ibu_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COLOR_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->color_min = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COLOR_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->color_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARB_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->carb_min = val;
+	    xmlFree(key);
+	}               
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARB_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->carb_max = val;
+	    xmlFree(key); 
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ABV_MIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->abv_min = val;
+	    xmlFree(key);
+	}              
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ABV_MAX"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->abv_max = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROFILE"))) {
+	    item->profile = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"INGREDIENTS"))) {
+	    item->ingredients = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EXAMPLES"))) {
+	    item->examples = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    *style = item;
+    return 0;
+}
+
+
+
+/*
+ * Parse equipment
+ */
+int parseEquipment(xmlDocPtr doc, xmlNodePtr cur, equipment_rec **equipment)
+{
+    xmlChar     	*key;
+    equipment_rec	*item;
+    float       	val;
+
+    item = (equipment_rec *)malloc(sizeof(equipment_rec));
+    item->name = item->notes = NULL;
+    item->version = 0;
+    item->boil_size = item->batch_size = item->tun_volume = item->tun_weight = 
+	    item->tun_specific_heat = item->top_up_water = item->trub_chiller_loss = 
+	    item->evap_rate = item->boil_time = item->lauter_deadspace =
+	    item->top_up_kettle = item->hop_utilization = 0.0;
+    item->calc_boil_volume = FALSE;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    xmlFree(key);
+	    item->version = 1;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->boil_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BATCH_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->batch_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_VOLUME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->tun_volume = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->tun_weight = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_SPECIFIC_HEAT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->tun_specific_heat = val;    
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TOP_UP_WATER"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->top_up_water = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TRUB_CHILLER_LOSS"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->trub_chiller_loss = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EVAP_RATE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->evap_rate = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->boil_time = val;
+	    xmlFree(key);        
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CALC_BOIL_VOLUME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->calc_boil_volume = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LAUTER_DEADSPACE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->lauter_deadspace = val;
+	    xmlFree(key);        
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOP_UTILIZATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->hop_utilization = val;
+	    xmlFree(key);        
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    *equipment = item;
+    return 0;
+}
+
+
+
+/*
+ * Parse a Mash step and add it to the linked list
+ */
+int parseMashStep(xmlDocPtr doc, xmlNodePtr cur, mash_step **mashstep)
+{
+    xmlChar     *key;
+    mash_step	*step, *tmp;
+    float	val;
+
+    cur = cur->xmlChildrenNode;
+
+    step = (mash_step *)malloc(sizeof(mash_step));
+    step->next = NULL;
+    step->name = NULL;
+    step->type = NULL;
+    step->infuse_amount = 0.0;
+    step->step_temp = 0.0;
+    step->end_temp = 0.0;
+    step->step_time = 0.0;
+    step->ramp_time = 0.0;
+    step->version = 0;
+
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    step->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    step->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    step->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"INFUSE_AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->infuse_amount = val;
+	    xmlFree(key);                           
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STEP_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->step_temp = val;
+	    xmlFree(key);                           
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STEP_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->step_time = val;
+	    xmlFree(key);                           
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"END_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->end_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RAMP_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		step->ramp_time = val;
+	    xmlFree(key);
+	}
+
+	cur = cur->next;
+    }
+	
+    if (*mashstep == NULL) {
+	*mashstep = step;
+    } else {
+	for (tmp = *mashstep; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = step;
+		break;
+	    }
+	}
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse all Mash steps
+ */
+int parseMashSteps(xmlDocPtr doc, xmlNodePtr cur, mash_step **step)
+{
+    int		rc;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASH_STEP"))) {
+	    if ((rc = parseMashStep(doc, cur, step)))
+		return 1;
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Mash profile
+ */
+int parseMash(xmlDocPtr doc, xmlNodePtr cur, mash_profile **mash)
+{
+    xmlChar     *key;
+    mash_profile	*mashprofile;
+    float	val;
+
+    mashprofile = (mash_profile *)malloc(sizeof(mash_profile));
+    mashprofile->name = NULL;
+    mashprofile->version = 0;
+    mashprofile->notes = NULL;
+    mashprofile->grain_temp = 0.0;
+    mashprofile->mash_steps = NULL;
+    mashprofile->tun_temp = 0.0;
+    mashprofile->sparge_temp = 0.0;
+    mashprofile->ph = 0.0;
+    mashprofile->tun_weight = 0.0;
+    mashprofile->tun_specific_heat = 0.0;
+    mashprofile->equip_adjust = FALSE;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    mashprofile->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    mashprofile->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    mashprofile->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"GRAIN_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->grain_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->tun_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SPARGE_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->sparge_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->ph = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->tun_weight = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TUN_SPECIFIC_HEAT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		mashprofile->tun_specific_heat = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EQUIP_ADJUST"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		mashprofile->equip_adjust = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASH_STEPS"))) {
+	    parseMashSteps(doc, cur, &(mashprofile)->mash_steps);
+	}
+	cur = cur->next;
+    }
+
+    *mash = mashprofile;
+    return 0;
+}
+
+
+
+/*
+ * Parse fermentable
+ */
+int parseFermentable(xmlDocPtr doc, xmlNodePtr cur, fermentable_rec **fr)
+{
+    xmlChar     	*key;
+    fermentable_rec	*item, *tmp;
+    float               val;
+
+    item = (fermentable_rec *)malloc(sizeof(fermentable_rec));
+    item->next = NULL;
+    item->name = item->type = item->notes = item->origin = item->supplier = NULL;
+    item->version = 0;
+    item->amount = item->yield = item->color = item->coarse_fine_diff = item->moisture = 
+	    item->diastatic_power = item->protein = item->max_in_batch = 0.0;
+    item->add_after_boil = item->recommend_mash = FALSE;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ORIGIN"))) {
+	    item->origin = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SUPPLIER"))) {
+	    item->supplier = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YIELD"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->yield = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COLOR"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->color = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ADD_AFTER_BOIL"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->add_after_boil = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COARSE_FINE_DIFF"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->coarse_fine_diff = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MOISTURE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->moisture = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DIASTATIC_POWER"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->diastatic_power = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PROTEIN"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->protein = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAX_IN_BATCH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->max_in_batch = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECOMMEND_MASH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->recommend_mash = TRUE;
+	}
+	cur = cur->next;
+    }
+
+    if (*fr == NULL) {
+	*fr = item;
+    } else {
+	for (tmp = *fr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse fermentables
+ */
+int parseFermentables(xmlDocPtr doc, xmlNodePtr cur, fermentable_rec **fr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTABLE"))) {
+	    parseFermentable(doc, cur, fr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse hop
+ */
+int parseHop(xmlDocPtr doc, xmlNodePtr cur, hop_rec **hr)
+{
+    xmlChar     *key;
+    hop_rec	*item, *tmp;
+    float       val;
+
+    item = (hop_rec *)malloc(sizeof(hop_rec));
+    item->next = NULL;
+    item->name = item->use = item->notes = item->type = item->form = 
+	    item->origin = item->substitutes = NULL;
+    item->version = 0;
+    item->alpha = item->amount = item->time = item->beta = item->hsi = 
+	    item->humulene = item->caryophyllene = item->cohumulone = item->myrcene = 0.0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ALPHA"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->alpha = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"USE"))) {
+	    item->use = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->time = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FORM"))) {
+	    item->form = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BETA"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->beta = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HSI"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->hsi = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ORIGIN"))) {
+	    item->origin = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SUBSTITITES"))) {
+	    item->substitutes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HUMULENE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->humulene = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARYOPHYLLENE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->caryophyllene = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"COHUMULONE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->cohumulone = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MYRCENE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->myrcene = val;
+	    xmlFree(key);
+	}
+	cur = cur->next;
+    }
+
+    if (*hr == NULL) {
+	*hr = item;
+    } else {
+	for (tmp = *hr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse hops
+ */
+int parseHops(xmlDocPtr doc, xmlNodePtr cur, hop_rec **hr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOP"))) {
+	    parseHop(doc, cur, hr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Yeast
+ */
+int parseYeast(xmlDocPtr doc, xmlNodePtr cur, yeast_rec **yr)
+{
+    xmlChar     *key;
+    yeast_rec	*item, *tmp;
+    float       val;
+    int		ival;
+
+    item = (yeast_rec *)malloc(sizeof(yeast_rec));
+    item->next = NULL;
+    item->name = item->type = item->form = item->laboratory = item->product_id = item->flocculation = item->notes = item->best_for = NULL;
+    item->amount = item->min_temperature = item->max_temperature = item->attenuation = 0.0;
+    item->amount_is_weight = item->add_to_secondary = FALSE;
+    item->version = item->times_cultured = item->max_reuse = 0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FORM"))) {
+	    item->form = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT_IS_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->amount_is_weight = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"LABORATORY"))) {
+	    item->laboratory = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRODUCT_ID"))) {
+	    item->product_id = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MIN_TEMPERATURE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->min_temperature = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAX_TEMPERATURE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->max_temperature = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FLOCCULATION"))) {
+	    item->flocculation = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ATTENUATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->attenuation = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BEST_FOR"))) {
+	    item->best_for = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIMES_CULTURED"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		item->times_cultured = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAX_REUSE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		item->max_reuse = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ADD_TO_SECONDARY"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->add_to_secondary = TRUE;
+	}
+	cur = cur->next;
+    }
+
+    if (*yr == NULL) {
+	*yr = item;
+    } else {
+	for (tmp = *yr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse Yeasts
+ */
+int parseYeasts(xmlDocPtr doc, xmlNodePtr cur, yeast_rec **yr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YEAST"))) {
+	    parseYeast(doc, cur, yr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Misc
+ */
+int parseMisc(xmlDocPtr doc, xmlNodePtr cur, misc_rec **mr)
+{
+    xmlChar     *key;
+    misc_rec   	*item, *tmp;
+    float       val;
+
+    item = (misc_rec *)malloc(sizeof(misc_rec));
+    item->next = NULL;
+    item->name = item->type = item->use = item->use_for = item->notes = NULL;
+    item->amount = item->time = 0.0;
+    item->amount_is_weight = FALSE;
+    item->version = 0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    item->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"USE"))) {
+	    item->use = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->time = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT_IS_WEIGHT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		item->amount_is_weight = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"USE_FOR"))) {
+	    item->use_for = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    if (*mr == NULL) {
+	*mr = item;
+    } else {
+	for (tmp = *mr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse Miscs
+ */
+int parseMiscs(xmlDocPtr doc, xmlNodePtr cur, misc_rec **mr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MISC"))) {
+	    parseMisc(doc, cur, mr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse Water
+ */
+int parseWater(xmlDocPtr doc, xmlNodePtr cur, water_rec **wr)
+{
+    xmlChar     *key;
+    water_rec   *item, *tmp;
+    float       val;
+
+    item = (water_rec *)malloc(sizeof(water_rec));
+    item->next = NULL;
+    item->name = item->notes = NULL;
+    item->amount = item->calcium = item->bicarbonate = item->sulfate = item->chloride = item->sodium = item->magnesium = item->ph = 0.0;
+    item->version = 0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"VERSION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (xmlStrcmp(key, (const xmlChar *)"1")) {
+		xmlFree(key);
+		return 1;
+	    }
+	    item->version = 1;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    item->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AMOUNT"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->amount = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CALCIUM"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->calcium = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BICARBONATE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->bicarbonate = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SULFATE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->sulfate = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CHLORIDE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->chloride = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SODIUM"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->sodium = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MAGNESIUM"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->magnesium = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PH"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		item->ph = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    item->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	cur = cur->next;
+    }
+
+    if (*wr == NULL) {
+	*wr = item;
+    } else {
+	for (tmp = *wr; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = item;
+		break;
+	    }
+	}
+    }
+
+    return 0;
+}
+
+
+
+/*
+ * Parse Waters
+ */
+int parseWaters(xmlDocPtr doc, xmlNodePtr cur, water_rec **wr)
+{
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"WATER"))) {
+	    parseWater(doc, cur, wr);
+	}
+	cur = cur->next;
+    }
+    return 0;
+}
+
+
+
+/*
+ * Parse a recipe
+ */
+void parseRecipe(xmlDocPtr doc, xmlNodePtr cur)
+{
+    xmlChar	*key;
+    recipe_rec	*recipe, *tmp;
+    float	val;
+    int		ival;
+
+    recipe = (recipe_rec *)malloc(sizeof(recipe_rec));
+    recipe->next = NULL;
+    recipe->name = NULL;
+    recipe->brewer = NULL;
+    recipe->asst_brewer = NULL;
+    recipe->type = NULL;
+    recipe->style = NULL;
+    recipe->equipment = NULL;
+    recipe->batch_size = 0.0;
+    recipe->boil_time = 0.0;
+    recipe->efficiency = 0.0;
+    recipe->hops = NULL;
+    recipe->fermentables = NULL;
+    recipe->miscs = NULL;
+    recipe->yeasts = NULL;
+    recipe->waters = NULL;
+    recipe->notes = NULL;
+    recipe->taste_notes = NULL;
+    recipe->taste_rating = 0.0;
+    recipe->og = 0.0;
+    recipe->fg = 0.0;
+    recipe->fermentation_stages = 0;
+    recipe->primary_age = recipe->secondary_age = recipe->tertiary_age = recipe->age = 0;
+    recipe->primary_temp = recipe->secondary_temp = recipe->tertiary_temp = recipe->age_temp = 0.0;
+    recipe->date = NULL;
+    recipe->carbonation = 0.0;
+    recipe->forced_carbonation = FALSE;
+    recipe->priming_sugar_name = NULL;
+    recipe->carbonation_temp = 0.0;
+    recipe->priming_sugar_equiv = recipe->keg_priming_factor = 0.0;
+
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NAME"))) {
+	    recipe->name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TYPE"))) {
+	    recipe->type = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"STYLE"))) {
+	    parseStyle(doc, cur, &(recipe)->style);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EQUIPMENT"))) {
+	    parseEquipment(doc, cur, &(recipe)->equipment);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BREWER"))) {
+	    recipe->brewer = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"ASST_BREWER"))) {
+	    recipe->asst_brewer = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BATCH_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->batch_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_SIZE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->boil_size = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"BOIL_TIME"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->boil_time = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"EFFICIENCY"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->efficiency = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"HOPS"))) {
+	    parseHops(doc, cur, &(recipe)->hops);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTABLES"))) {
+	    parseFermentables(doc, cur, &(recipe)->fermentables);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MISCS"))) {
+	    parseMiscs(doc, cur, &(recipe)->miscs);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"YEASTS"))) {
+	    parseYeasts(doc, cur, &(recipe)->yeasts);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"WATERS"))) {
+	    parseWaters(doc, cur, &(recipe)->waters);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"MASH"))) {
+	    parseMash(doc, cur, &(recipe)->mash);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"NOTES"))) {
+	    recipe->notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TASTE_NOTES"))) {
+	    recipe->taste_notes = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TASTE_RATING"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->taste_rating = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"OG"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->og = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FG"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->fg = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FERMENTATION_STAGES"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->fermentation_stages = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMARY_AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->primary_age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMARY_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->primary_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SECONDARY_AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->secondary_age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"SECONDARY_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->secondary_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TERTIARY_AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->tertiary_age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"TERTIARY_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->tertiary_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AGE"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%d", &ival) == 1)
+		recipe->age = ival;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"AGE_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->age_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"DATE"))) {
+	    recipe->date = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARBONATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->carbonation = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"FORCED_CARBONATION"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (! xmlStrcmp(key, (const xmlChar *)"TRUE"))
+		recipe->forced_carbonation = TRUE;
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMING_SUGAR_NAME"))) {
+	    recipe->priming_sugar_name = (char *)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"CARBONATION_TEMP"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->carbonation_temp = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"PRIMING_SUGAR_EQUIV"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->priming_sugar_equiv = val;
+	    xmlFree(key);
+	}
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"KEG_PRIMING_FACTOR"))) {
+	    key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
+	    if (sscanf((const char *)key, "%f", &val) == 1)
+		recipe->keg_priming_factor = val;
+	    xmlFree(key);
+	}
+	cur = cur->next;
+    }
+
+    if (recipes == NULL) {
+	recipes = recipe;
+    } else {
+	for (tmp = recipes; tmp; tmp = tmp->next) {
+	    if (tmp->next == NULL) {
+		tmp->next = recipe;
+		break;
+	    }
+	}
+    }
+}
+
+
+
+int parseBeerXML(char *docname)
+{
+    xmlDocPtr		doc;
+    xmlNodePtr		cur;
+
+    if ((doc = xmlParseFile(docname)) == NULL) {
+	fprintf(stderr, "XML file %s errors.\n", docname);
+	return 1;
+    }
+
+    if ((cur = xmlDocGetRootElement(doc)) == NULL) {
+	fprintf(stderr, "XML file %s empty.\n", docname);
+	xmlFreeDoc(doc);
+	return 1;
+    }
+
+    if (xmlStrcmp(cur->name, (const xmlChar*)"RECIPES")) {
+	fprintf(stderr, "XML file %s is not a BeerXML file.\n", docname);
+	xmlFreeDoc(doc);
+	return 1;
+    }
+
+    /*
+     * Parse all recipes
+     */
+    cur = cur->xmlChildrenNode;
+    while (cur != NULL) {
+	if ((!xmlStrcmp(cur->name, (const xmlChar *)"RECIPE"))) {
+	    parseRecipe(doc, cur);
+	}
+	cur = cur->next;
+    }
+
+    xmlFreeDoc(doc);
+    return 0;
+}
+
+
+
+/*
+ * For debugging purposes, dump the recipes
+ */
+void printBeerXML(void)
+{
+    recipe_rec		*tmp0;
+    mash_step           *tmp1;
+    fermentable_rec     *tmp2;
+    equipment_rec	*tmp3;
+    style_rec		*tmp4;
+    hop_rec		*tmp5;
+    yeast_rec		*tmp6;
+    misc_rec		*tmp7;
+    water_rec		*tmp8;
+    int			mashstep = 0;
+    float               grain_weight = 0.0, water_grain_ratio, tun_water = 0.0, temp;
+
+    for (tmp0 = recipes; tmp0; tmp0 = tmp0->next) {
+	printf("--------------------------------------------------------------------------------\n");
+    	printf("Recipe:     %s\n", tmp0->name);
+    	printf("Type:       %s\n", tmp0->type);
+	if (tmp0->notes)
+	    printf("Notes:      %s\n", tmp0->notes);
+	if (tmp0->brewer || tmp0->asst_brewer)
+    	    printf("Brewer:     %s  Assistent brewer: %s\n", tmp0->brewer, tmp0->asst_brewer);
+	printf("Batch size: %5.1f  Boil time:  %5.1f  Efficiency: %5.1f\n", tmp0->batch_size, tmp0->boil_time, tmp0->efficiency);
+	printf("Taste rate: %5.1f  Taste notes: %s\n", tmp0->taste_rating, tmp0->taste_notes);
+	printf("Original G: %5.3f  Final G:    %5.3f\n", tmp0->og, tmp0->fg);
+    	printf("\n");
+
+	if (tmp0->equipment) {
+	    tmp3 = tmp0->equipment;
+	    printf("Equipment:  %s\n", tmp3->name);
+	    if (tmp3->notes)
+	    	printf("  Notes:     %s\n", tmp3->notes);
+	    printf("  Boil size:  %5.1f  Batch size:   %5.1f  Boil time:        %5.1f\n", tmp3->boil_size, tmp3->batch_size, tmp3->boil_time);
+	    printf("  Tun volume: %5.1f  Tun weight:   %5.1f  Specific heat:    %5.1f\n", tmp3->tun_volume, tmp3->tun_weight, tmp3->tun_specific_heat);
+	    printf("  TopUp:      %5.1f  Chiller loss: %5.1f  Evaporation rate: %5.1f\n", tmp3->top_up_water, tmp3->trub_chiller_loss, tmp3->evap_rate);
+	    printf("  Tun loss:   %5.1f  TopUp kettle: %5.1f  Hop utilization:  %5.1f\n", tmp3->lauter_deadspace, tmp3->top_up_kettle, tmp3->hop_utilization);
+	    printf("  CalcBoilVol:  %s\n", tmp3->calc_boil_volume ? "YES":"NO");
+	    printf("\n");
+	}
+
+	if (tmp0->style) {
+	    tmp4 = tmp0->style;
+	    printf("Style:      %s-%s Type:   %s\n", tmp4->style_letter, tmp4->name, tmp4->type); 
+	    if (tmp4->category || tmp4->category_number || tmp4->style_guide)
+	    	printf(" Category:  %s    Cat nr: %s  Guide: %s\n", tmp4->category, tmp4->category_number, tmp4->style_guide);
+	    printf("  OG:        %5.3f - %5.3f\n", tmp4->og_min, tmp4->og_max);
+	    printf("  FG:        %5.3f - %5.3f\n", tmp4->fg_min, tmp4->fg_max);
+	    printf("  Bittern:   %5.1f - %5.1f IBU\n", tmp4->ibu_min, tmp4->ibu_max);
+	    printf("  Color:     %5.1f - %5.1f SRM\n", tmp4->color_min, tmp4->color_max);
+	    printf("  Carb:      %5.1f - %5.1f V-CO2\n", tmp4->carb_min, tmp4->carb_max);
+	    printf("  ABV:       %5.1f - %5.1f %%\n", tmp4->abv_min, tmp4->abv_max);
+	    if (tmp4->notes)
+	    	printf("  Notes:     %s\n", tmp4->notes);
+	    if (tmp4->profile)
+	    	printf("  Profile:   %s\n", tmp4->profile);
+	    if (tmp4->examples)
+	    	printf("  Examples:  %s\n", tmp4->examples);
+	    printf("\n");
+	}
+
+    	printf("Fermentables:\n");
+    	printf("  Type         Name                       Kg    Mash  Moist\n");
+    	for (tmp2 = tmp0->fermentables; tmp2; tmp2 = tmp2->next) {
+	    printf("  %-12s %-25s %6.3f %-5s %5.1f\n", tmp2->type, tmp2->name, tmp2->amount,
+			tmp2->recommend_mash ? "TRUE":"FALSE", tmp2->moisture);
+	    if (tmp2->recommend_mash)
+	    	grain_weight += tmp2->amount;
+        }
+    	printf("\n");
+
+	if (tmp0->hops) {
+	    printf("Hops:\n");
+	    //        123456789 1234567890 1234567890123456789012345 123456 12345 12345
+	    printf("  Type      Use        Name                          Kg  Time Alpha\n");
+	    for (tmp5 = tmp0->hops; tmp5; tmp5 = tmp5->next) {
+	    	printf("  %-9s %-10s %-25s %6.3f %5.1f %5.1f\n", tmp5->type, tmp5->use, tmp5->name, tmp5->amount, tmp5->time, tmp5->alpha);
+	    }
+	    printf("\n");
+	}
+
+	if (tmp0->miscs) {
+	    printf("Miscs:\n");
+	    //        12345678901 123456789 1234567890123456789012345 123456 12345 12345
+	    printf("  Type        Use       Name                          Kg  Time\n");
+	    for (tmp7 = tmp0->miscs; tmp7; tmp7 = tmp7->next) {
+		printf("  %-11s %-9s %-25s %6.3f %5.1f\n", tmp7->type, tmp7->use, tmp7->name, tmp7->amount, tmp7->time);
+	    }
+	    printf("\n");
+	}
+
+	if (tmp0->waters) {
+	    printf("Wateren:\n");
+	    //        1234567890123456789012345 12345 12345 1234...
+	    printf("  Name                       Ltrs    PH Notes\n");
+	    for (tmp8 = tmp0->waters; tmp8; tmp8 = tmp8->next) {
+		printf("  %-25s %5.1f %5.1f %s\n", tmp8->name, tmp8->amount, tmp8->ph, tmp8->notes);
+	    }
+	    printf("\n");
+	}
+
+    	printf("Mash profile:   %s\n", tmp0->mash->name);
+	if (tmp0->mash->notes)
+    	    printf("  Notes:        %s\n", tmp0->mash->notes);
+    	printf("  Grain temp:  %5.1f   Tun temp:   %5.1f    Sparge temp:       %5.1f\n", tmp0->mash->grain_temp, tmp0->mash->tun_temp, tmp0->mash->sparge_temp);
+    	printf("  PH:          %5.1f   Tun weight: %6.2f   Tun specific heat: %6.2f\n", tmp0->mash->ph, tmp0->mash->tun_weight, tmp0->mash->tun_specific_heat);
+    	printf("  Equip adjust: %s\n", tmp0->mash->equip_adjust ? "TRUE":"FALSE");
+    	printf("\n");
+
+	if (tmp0->mash->mash_steps) {
+    	    mashstep = 0;
+    	    printf("st Name             Type          Temp  Endtmp Dur Rmp Inf/Dec  L/Kg\n");
+    	    for (tmp1 = tmp0->mash->mash_steps; tmp1; tmp1 = tmp1->next) {
+	    	mashstep++;
+	    	tun_water += tmp1->infuse_amount;
+	    	water_grain_ratio = tun_water / grain_weight;
+	    	temp = tmp1->infuse_amount;
+//	    	if ((tmp1->infuse_amount == 0.0) && (tmp1->decotion_amt > 0.0))
+//		    temp = tmp1->decotion_amt;
+	    	printf("%2d %-16s %-12s %6.1f %6.1f %3.0f %3.0f %6.1f %6.2f\n", mashstep, tmp1->name, tmp1->type, 
+			tmp1->step_temp, tmp1->end_temp, tmp1->step_time, tmp1->ramp_time, temp, water_grain_ratio);
+            }
+	    printf("\n");
+	}
+
+	if (tmp0->yeasts) {
+	    for (tmp6 = tmp0->yeasts; tmp6; tmp6 = tmp6->next) {
+		printf("Yeast:       %s\n", tmp6->name);
+		printf("  Type:      %-20s Form:       %-20s Amount:      %7.4f %s\n", 
+				tmp6->type, tmp6->form, tmp6->amount,  tmp6->amount_is_weight ? "Kg":"L");
+		printf("  Lab:       %-20s Product id: %-20s Flocculation: %s\n", tmp6->laboratory, tmp6->product_id, tmp6->flocculation);
+		printf("  Min temp: %5.1f                 Max temp:  %5.1f                 Attenuation: %5.1f\n", 
+				tmp6->min_temperature, tmp6->max_temperature, tmp6->attenuation);
+		if (tmp6->notes)
+		    printf("  Notes:     %s\n", tmp6->notes);
+		printf("\n");
+	    }
+	}
+
+	printf("\n");
+    }
+}
+
+

mercurial