mash/beerxml.c

Fri, 11 Mar 2016 20:27:02 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 11 Mar 2016 20:27:02 +0100
changeset 492
750f2468dec5
parent 103
99c47a8a61cb
permissions
-rw-r--r--

Changed PID code. PID parameters are now stored 3 digits instead of 2 behind the decimal point. Prevent extreme heating or cooling in Beer mode. Heat and Cool lockdown now allows the lagest value to win instead of zero them both. PID output treshold from 2% to 50%.

/*****************************************************************************
 * 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