#define _GNU_SOURCE			/* for vasprintf */
#include "squaredance.h"

#include <stdarg.h>
#include <libxml2/libxml/parser.h>
#include <assert.h>

/* Paramaterized circles
 * Only one role which will be adjusted for the circle size and then duplicated
 * circle types:
 *  Everyone on circumference (bring me flowers)
 *  Two concentric circles, men on outside ladies in
 *  Sicilian Circle, concentric circles, alternate couples improper facing backwards
 * Specify the center of the circle, and number of couples for which the role
 *  is actually written. We will then transform the circle to make it larger
 *  or smaller depending on the number of couples actually dancing. We will
 *  also need to adjust the angles depending on the number of people.
 * Think of x,y coords in terms of r,theta
 *  We can transform either r or theta or both.
 *  Basic transform, operations with partner probably only need to be translated
 *   to fit the new circle size
 *  Circle half way round still needs to go halfway round so theta stays the same
 *  Circle 2 bars needs to go a certain distance which will make theta smaller
 *   as the circle gets bigger.
 *  Progress, move to next location, will involve a different change of theta
 *   as the circle gets bigger (is this the same as the previous, perhaps)
 * Orientation needs to be adjusted (how?) when copying roles, I think I've
 *  already solved that.
 * Hand clasps need to be adjusted. Don't give hand clasps in terms of the
 *  circle being emulated, but rather in terms of couples skipped. So in
 *  bring me flowers the man moves 4 places (couples) one way while the woman
 *  moves 4 the other way, meaning the new partner is 8 couples off, so 16 hand
 *  clasps
 */

static char *lastError=NULL;

static void setError(char *frmt, ... ) {
    va_list ap;

    free(lastError);
    lastError=NULL;
    if ( frmt==NULL )
return;

    va_start(ap,frmt);
    vasprintf( &lastError, frmt, ap );
    va_end(ap);
    fprintf( stderr, "%s\n", lastError );
}

char *danceLoaderError(void) {
return( lastError );
}

static char *gxmlGetEmptyProp(xmlNodePtr xml, char *nodename) {
    xmlChar *temp = xmlGetProp(xml,(xmlChar *) nodename);

    if ( temp==NULL )
return( NULL );
    if ( *temp=='\0' ) {
	xmlFree(temp);
return( NULL );
    }
return( (char *) temp );
}

static int gxmlGetBooleanProp(xmlNodePtr xml, char *attrname, char *altname, int defaultVal) {
    xmlChar *temp;
    int val = defaultVal;

    temp = xmlGetProp(xml,(xmlChar *) attrname);
    if ( temp==NULL && altname!=NULL )
	temp = xmlGetProp(xml,(xmlChar *) altname);

    if ( temp!=NULL ) {
	if ( strcasecmp((char *) temp,"yes")==0 || strcasecmp((char *) temp,"on")==0  || strcasecmp((char *) temp,"do")==0 || strcasecmp((char *) temp,"true")==0 )
	    val = true;
	else if ( strcasecmp((char *) temp,"no")==0 || strcasecmp((char *) temp,"false")==0 || strcasecmp((char *) temp,"off")==0  || strcasecmp((char *) temp,"dont")==0 || strcasecmp((char *) temp,"don't")==0 )
	    val = false;
	else if ( isdigit(*temp))
	    val = strtol((char *) temp,NULL,10);
	else
	    fprintf( stderr, "Unknown value for '%s' \"%s\".\n", attrname, temp );
	xmlFree(temp);
    }
return( val );
}

static int gxmlCountKids(xmlNodePtr xml, char *nodename1, char *nodename2, char *nodename3) {
    xmlNodePtr kid;
    int cnt=0;

    for ( kid = xml->children; kid!=NULL; kid=kid->next ) {
	if ( xmlStrcmp(kid->name,(xmlChar *) nodename1 )==0 )
	    ++cnt;
	else if ( nodename2!=NULL && xmlStrcmp(kid->name,(xmlChar *) nodename2 )==0 )
	    ++cnt;
	else if ( nodename3!=NULL && xmlStrcmp(kid->name,(xmlChar *) nodename3 )==0 )
	    ++cnt;
    }
return( cnt );
}

/* For legacy files */
static unsigned int parseOClasp(const char *filename, char *temp, FigureSegment *cs, xmlNodePtr node) {
    unsigned int mask = 0;
    int off=1;
    char *end;
    int i, len;
    int templen = strlen(temp);

    while ( *temp ) {
	int found = 0;
	for ( i=0; handMaskMap[i].name!=NULL; ++i ) {
	    len = strlen(handMaskMap[i].name);
	    if ( len<templen && temp[len]==':' && strncasecmp(temp,handMaskMap[i].name,len)==0 ) {
		mask |= handMaskMap[i].mask;
		temp += len+1;
		templen -= len+1;
		found=1;
	break;
	    }
	}
	if ( !found )
    break;
    }

    if ( strncasecmp(temp,"star:",5)==0 ) {
	sscanf(temp+5,"%f,%f", &cs->star_center.x, &cs->star_center.y );
return( HAND_STAR|mask );
    }

    if ( isdigit(*temp))
return( strtol(temp,NULL,0)|mask);

    if ( strncasecmp(temp,"up",2)==0 ) {
	if ( isdigit(temp[2]))
	    off = strtol(temp+2,&end,10);
	else {
	    end = temp+2;
	    off = 1;
	}
	mask |= HAND_MINORSET_UP;
    } else if ( strncasecmp(temp,"down",4)==0 ) {
	if ( isdigit(temp[4]))
	    off = strtol(temp+4,&end,10);
	else {
	    end = temp+4;
	    off = 1;
	}
    } else
	end="";
    if ( *end!=':' ) {
	setError("In file %s:%ld, bad hand clasp: %s.", filename, xmlGetLineNo(node), temp );
return( -1 );
    }
    ++end;
    if ( strncasecmp(end,"notout:",7)==0 ) {
	mask |= HAND_NOTOUT_MASK;
	end = end+7;
    }

    mask |= off<<HAND_MINORSET_SHIFT;
return( strtol(end,NULL,0) | mask );
}

static unsigned int parseClasp(const char *filename, char *temp, FigureSegment *cs, unsigned short *millibars, xmlNodePtr node) {
    unsigned int mask = 0, ret;
    int off=1;
    char *end;
    int i, len;
    int templen = strlen(temp);

    while ( *temp ) {
	int found=0;
	for ( i=0; handMaskMap[i].name!=NULL; ++i ) {
	    len = strlen(handMaskMap[i].name);
	    if ( len<templen && temp[len]==':' && strncasecmp(temp,handMaskMap[i].name,len)==0 ) {
		mask |= handMaskMap[i].mask;
		temp += len+1;
		templen -= len+1;
		found=1;
	break;
	    }
	}
	if ( !found )
    break;
    }

    if ( strncasecmp(temp,"star:",5)==0 ) {
	double btemp;
	int cnt;
	cnt = sscanf(temp+5,"%f,%f|%lf", &cs->star_center.x, &cs->star_center.y, &btemp );
	if ( cnt != 3 ) {
	    if ( cnt==2 && strcasecmp(temp+strlen(temp)-4,"|end")==0 )
		*millibars = cs->millibars;
	    else {
		setError("In file %s:%ld, bad hand clasp (should be three things after star, x,y|bar): %s.", filename, xmlGetLineNo(node), temp );
return( -1 );
	    }
	} else
	    *millibars = MILLIBARS_MUL * btemp;
return( HAND_STAR|mask );
    } else if ( (mask&HAND_HAND_MASK)==0 ) {
	setError("In file %s:%ld, bad hand clasp (neither right nor left specified for other dancer): %s.", filename, xmlGetLineNo(node), temp );
return( -1 );
    }

    if ( isdigit(*temp)) {
	end = temp;
    } else {
	if ( strncasecmp(temp,"up",2)==0 ) {
	    if ( isdigit(temp[2]))
		off = strtol(temp+2,&end,10);
	    else {
		end = temp+2;
		off = 1;
	    }
	    mask |= HAND_MINORSET_UP;
	    if ( strncasecmp(end,":notout",7)==0 ) {
		mask |= HAND_NOTOUT_MASK;
		end = end+7;
	    }
	} else if ( strncasecmp(temp,"down",4)==0 ) {
	    if ( isdigit(temp[4]))
		off = strtol(temp+4,&end,10);
	    else {
		end = temp+4;
		off = 1;
	    }
	    if ( strncasecmp(end,":notout",7)==0 ) {
		mask |= HAND_NOTOUT_MASK;
		end = end+7;
	    }
	} else {
	    setError("In file %s:%ld, bad hand clasp: %s.", filename, xmlGetLineNo(node), temp );
return( -1 );
	}

	if ( *end!=':' ) {
	    setError("In file %s:%ld, bad hand clasp: %s.", filename, xmlGetLineNo(node), temp );
return( -1 );
	}
	++end;
	mask |= off<<HAND_MINORSET_SHIFT;
    }
    ret = strtol(end,&end,0) | mask;
    if ( *end=='|' ) {
	if ( strcasecmp(end+1,"end")==0 || strcasecmp(end+1,"after")==0 )
	    *millibars = cs->millibars;
	else
	    *millibars = strtod(end+1,NULL)*MILLIBARS_MUL;
return( ret ) ;
    } else  {
	setError("In file %s:%ld, bad hand clasp (missing bar cnt): %s.", filename, xmlGetLineNo(node), temp );
return( -1 );
    }
}

static ValChange *parseValueChange(const char *filename, char *temp, int millibars_at_end, xmlNodePtr node) {
    char *pt, *end;
    int comma_cnt=0;
    ValChange *ret;

    if ( temp==NULL || *temp=='\0' )
return( NULL );
    for ( pt=temp; *pt!='\0' ; ++pt )
	if ( *pt==',' || *pt==';' )
	    ++comma_cnt;
    ret = calloc(comma_cnt+2,sizeof(ValChange));
    comma_cnt=0;
    for ( pt=temp; *pt!='\0'; ) {
	ret[comma_cnt].value = strtol(pt,&end,10);
	if ( ret[comma_cnt].value<0 || ret[comma_cnt].value>100 ) {
	    setError("In file %s:%ld, value out of bounds: %s.", filename, xmlGetLineNo(node), temp );
	    free(ret);
return( NULL );
	}
	if ( *end=='\0' )
	    ret[comma_cnt].millibars = millibars_at_end;
	else if ( *end!='|' ) {
	    setError("In file %s:%ld, no millibar for value: %s.", filename, xmlGetLineNo(node), temp );
	    free(ret);
return( NULL );
	} else if ( strncasecmp(end+1,"end",3)==0 ) {
	    ret[comma_cnt].millibars = millibars_at_end;
	    end += 4;
	} else {
	    ret[comma_cnt].millibars = round(MILLIBARS_MUL*strtod(end+1,&end));
	}
	++comma_cnt;
	if ( *end=='\0' )
    break;
	else if ( *end!=',' && *end!=';' ) {
	    setError("In file %s:%ld, missing comma in value list: %s.", filename, xmlGetLineNo(node), temp );
	    free(ret);
return( NULL );
	}
	pt = end+1;
    }
    ret[comma_cnt].millibars = -1;
return( ret );
}

static RThetaChange *parseRThetaChange(const char *filename, char *temp, int millibars_at_end, xmlNodePtr node) {
    char *pt, *end;
    int comma_cnt=0;
    RThetaChange *ret;

    if ( temp==NULL || *temp=='\0' )
return( NULL );
    for ( pt=temp; *pt!='\0' ; ++pt )
	if ( *pt==',' || *pt==';' )
	    ++comma_cnt;
    ret = calloc(comma_cnt+2,sizeof(RThetaChange));	/* comma_cnt/2 would be better */
    comma_cnt=0;
    for ( pt=temp; *pt!='\0'; ) {
	ret[comma_cnt].pos.r = strtod(pt,&end);
	if ( ret[comma_cnt].pos.r<0 || ret[comma_cnt].pos.r>2 ) {
	    setError("In file %s:%ld, r out of bounds: %s.", filename, xmlGetLineNo(node), pt );
	    free(ret);
return( NULL );
	}
	if ( *end!=',' ) {
	    setError("In file %s:%ld, r must be followed by a comma and a theta: %s.", filename, xmlGetLineNo(node), pt );
	    free(ret);
return( NULL );
	}
	pt = end+1;
	ret[comma_cnt].pos.theta = strtod(pt,&end);
	if ( ret[comma_cnt].pos.theta<-180 || ret[comma_cnt].pos.theta>360 ) {
	    setError("In file %s:%ld, theta out of bounds: %s.", filename, xmlGetLineNo(node), pt );
	    free(ret);
return( NULL );
	}
	if ( *end=='\0' )
	    ret[comma_cnt].millibars = millibars_at_end;
	else if ( *end!='|' ) {
	    setError("In file %s:%ld, no millibar for value: %s.", filename, xmlGetLineNo(node), temp );
	    free(ret);
return( NULL );
	} else if ( strncasecmp(end+1,"end",3)==0 ) {
	    ret[comma_cnt].millibars = millibars_at_end;
	    end += 4;
	} else {
	    ret[comma_cnt].millibars = round(MILLIBARS_MUL*strtod(end+1,&end));
	}
	++comma_cnt;
	if ( *end=='\0' )
    break;
	else if ( *end!=',' && *end!=';' ) {
	    setError("In file %s:%ld, missing comma in rtheta list: %s.", filename, xmlGetLineNo(node), temp );
	    free(ret);
return( NULL );
	}
	pt = end+1;
    }
    ret[comma_cnt].millibars = -1;
return( ret );
}

static int danceParseTransformation(const char *filename,xmlNodePtr copyCmdNode,struct transformation *trans) {
    xmlChar *temp;
    char *end;

    memset(trans,0,sizeof(struct transformation));
    trans->hand_mod = 400;
    trans->xscale = trans->yscale = 1;
    memset(trans->handmap,-1,sizeof(trans->handmap));

    if ( xmlHasProp(copyCmdNode, (xmlChar *) "transform") )
	fprintf( stderr, "%s:%ld Use of 'transform' as an attribute. You probably meant 'translate'.\n",filename, xmlGetLineNo(copyCmdNode));
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "translate");
    if ( temp!=NULL ) {
	trans->xoff = strtod((char *) temp,&end);
	if ( *end==',' ) ++end;
	trans->yoff = strtod(end,NULL);
	xmlFree(temp);
    }
    if ( xmlHasProp(copyCmdNode,(xmlChar *) "rotateto") )
	fprintf( stderr, "%s:%ld Use of 'rotateto' as an attribute. You probably meant 'rotate'.\n",filename, xmlGetLineNo(copyCmdNode));
    
    /* rotation is optional */
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "rotate");
    if ( temp!=NULL ) {
	trans->rotateby = strtod((char *) temp,&end);
	if ( *end!='@' && *end!='\0' )
	    fprintf( stderr, "%s:%ld No rotation center. You probably meant '@'.\n",filename, xmlGetLineNo(copyCmdNode));
	if ( *end=='@' ) ++end;
	trans->center_x = strtod(end,&end);
	if ( *end==',' ) ++end;
	else
	    fprintf( stderr, "%s:%ld Missing y value in rotation center. You probably want a comma somewhere.\n",filename, xmlGetLineNo(copyCmdNode));
	trans->center_y = strtod(end,NULL);
	xmlFree(temp);
    }

    /* This will never be generated on output, but is convenient for input */
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "hflip");
    if ( temp!=NULL ) {
	trans->flip_xcenter = 2*strtod((char *) temp,&end);
	xmlFree(temp);
	trans->dohflip = 1;
    }
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "vflip");
    if ( temp!=NULL ) {
	trans->flip_ycenter = 2*strtod((char *) temp,&end);
	xmlFree(temp);
	trans->dovflip = 1;
    }

    temp = xmlGetProp(copyCmdNode,(xmlChar *) "slide");
    if ( temp!=NULL ) {
	trans->x_slide = strtod((char *) temp,&end);
	if ( *end==',' ) ++end;
	trans->y_slide = strtod(end,NULL);
	xmlFree(temp);
    }

    /* format is scale="xscale[,yscale][@old_center_x[,old_center_y][@new_center_x[,new_center_y]]]" */
    /* so for any position, subtract the old center, do the scale, add the new center */
    /* if omitted new center defaults to old */
    /* if omitted old center defaults to origin */
    /* if omitted yscale defaults to xscale */
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "scale");
    if ( temp!=NULL ) {

	trans->xscale = strtod((char *) temp,&end);
	if ( *end==',' )
	    trans->yscale = strtod((char *) end+1,&end);
	else
	    trans->yscale = trans->xscale;
	trans->xcenter_old = trans->ycenter_old = 0;
	if ( *end=='@' ) {
	    trans->xcenter_old = strtod((char *) end+1,&end);
	    if ( *end==',' )
		trans->ycenter_old = strtod((char *) end+1,&end);
	}
	trans->xcenter_new = trans->xcenter_old;
	trans->ycenter_new = trans->ycenter_old;
	if ( *end=='@' ) {
	    trans->xcenter_new = strtod((char *) end+1,&end);
	    if ( *end==',' )
		trans->ycenter_new = strtod((char *) end+1,&end);
	}
	if ( *end!='\0' ) {
	    setError("In file %s:%ld, bad scale transformation \"%s\".", filename, xmlGetLineNo(copyCmdNode), temp );
	    xmlFree(temp);
return( 0 );
	}
	xmlFree(temp);
    }

    temp = xmlGetProp(copyCmdNode,(xmlChar *) "time-scale");
    if ( temp!=NULL ) {
	trans->timescale = strtod((char *) temp,&end);
	xmlFree(temp);
    }

    temp = xmlGetProp(copyCmdNode,(xmlChar *) "bar-scale");
    if ( temp!=NULL ) {
	trans->barscale = round(MILLIBARS_MUL*strtod((char *) temp,&end));
	xmlFree(temp);
    }

    temp = xmlGetProp(copyCmdNode,(xmlChar *) "unmap-hands");
    if ( temp!=NULL ) {
	int val;
	val = strtol((char *) temp,&end,10);
	trans->unmapped_hands |= (1L<<val);
	while ( *end==',' ) {
	    val = strtol(end+1,&end,10);
	    trans->unmapped_hands |= (1L<<val);
	}
	xmlFree(temp);
    }
    /* Add this number to the hand clasp, then calculate mod the second number */
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "hand-offset");
    if ( temp!=NULL ) {
	trans->hand_offset = strtol((char *) temp,&end,10);
	if ( *end=='%' )
	    trans->hand_mod = strtol(end+1,NULL,10);
	xmlFree(temp);
    }
    /* Most general of all, specify what happens to each dancer. Any dancer not */
    /*  mentioned gets unmapped */
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "remap-hands");
    if ( temp!=NULL ) {
	char *pt;
	trans->full_remap = 1;
	pt = (char *) temp;
	while ( *pt ) {
	    int new, old = strtol(pt,&end,10);
	    if ( *end==':' )
		++end;
	    else {
		setError("In file %s:%ld, bad remap-hands, expected ':' after %d in '%s'.", filename, xmlGetLineNo(copyCmdNode), old, temp );
return( false );
	    }
	    new = strtol(end, &end,10);
	    if ( old<sizeof(trans->handmap) )
		trans->handmap[old] = new;
	    pt = end;
	    if ( *pt==',' )
		++pt;
	    else if ( *pt=='\0' )
	break;
	    else {
		setError("In file %s:%ld, bad remap-hands, expected ',' after %d in '%s'.", filename, xmlGetLineNo(copyCmdNode), new, temp );
return( false );
	    }
	}
	xmlFree(temp);
    }

    temp = xmlGetProp(copyCmdNode,(xmlChar *) "repeat");
    if ( temp!=NULL ) {
	trans->repeat = strtol((char *) temp,&end,10);
	xmlFree(temp);
    } else
	trans->repeat = 1;

return( true );
}

static double myround(double x, double oldx, double angle, double centerx) {
    if ( fmod(oldx,32)==0 && fmod(centerx,32)==0 && angle==180 )
return( 32*round(x/32) );
	
return( round( x ));
}

static void danceMoveTransform(FigureSegment *cs,struct transformation *trans) {
    /* This is subtly different from the blockTransform (beyond working on a single move, I mean) */
    /* We do not always do what a literal interpretation of the transform would have us do */
    /* for instance with 'hflip="448" join-after-right-right="up:2"' */
    /*  a litteral flip would change the right clasp to a left and the up to a down */
    /* but I thought that would prove unexpected so I don't do it */
    int x, y;

    if ( trans->xoff!=0 || trans->yoff != 0 ) {
	if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
	    cs->endPoint.x += trans->xoff; cs->endPoint.y += trans->yoff;
	    if ( cs->cmd==c_curveto ) {
		cs->cp1.x += trans->xoff; cs->cp1.y += trans->yoff;
		cs->cp2.x += trans->xoff; cs->cp2.y += trans->yoff;
	    }
	}
	if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
	    cs->star_center.x += trans->xoff;
	    cs->star_center.y += trans->yoff;
	}
    }

    if ( trans->rotateby ) {
	double c = cos(trans->rotateby*PI/180), s=sin(trans->rotateby*PI/180);
	if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
	    x = myround(trans->center_x + c*(cs->endPoint.x-trans->center_x) - s*(cs->endPoint.y-trans->center_y),cs->endPoint.x,trans->rotateby, trans->center_x);
	    y = myround(trans->center_y + s*(cs->endPoint.x-trans->center_x) + c*(cs->endPoint.y-trans->center_y),cs->endPoint.y,trans->rotateby, trans->center_y);
	    cs->endPoint.x = x; cs->endPoint.y = y;
	    if ( cs->cmd==c_curveto ) {
		x = trans->center_x + c*(cs->cp1.x-trans->center_x) - s*(cs->cp1.y-trans->center_y);
		y = trans->center_y + s*(cs->cp1.x-trans->center_x) + c*(cs->cp1.y-trans->center_y);
		cs->cp1.x = myround(x,cs->cp1.x,trans->rotateby,trans->center_x); cs->cp1.y = myround(y,cs->cp1.y,trans->rotateby,trans->center_x);
		x = trans->center_x + c*(cs->cp2.x-trans->center_x) - s*(cs->cp2.y-trans->center_y);
		y = trans->center_y + s*(cs->cp2.x-trans->center_x) + c*(cs->cp2.y-trans->center_y);
		cs->cp2.x = myround(x,cs->cp2.x,trans->rotateby,trans->center_x); cs->cp2.y = myround(y,cs->cp2.y,trans->rotateby,trans->center_y);
	    }
	}
	if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
	    x = trans->center_x + c*(cs->star_center.x-trans->center_x) - s*(cs->star_center.y-trans->center_y);
	    y = trans->center_y + s*(cs->star_center.x-trans->center_x) + c*(cs->star_center.y-trans->center_y);
	    cs->star_center.x = x; cs->star_center.y = y;
	}
	if ( cs->cmd==c_moveto || cs->orient==mo_rotateto ||
		cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
	    if ( cs->degrees!=DEGREES_END ) {
		cs->degrees += trans->rotateby;
		if ( cs->degrees>=360 )
		    cs->degrees -= 360;
	    }
	}
    }

    if ( trans->dovflip ) {
	if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
	    cs->endPoint.y = trans->flip_ycenter- cs->endPoint.y;
	    if ( cs->cmd==c_curveto ) {
		cs->cp1.y = trans->flip_ycenter- cs->cp1.y;
		cs->cp2.y = trans->flip_ycenter- cs->cp2.y;
	    }
	}
	if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
	    cs->star_center.y = trans->flip_ycenter- cs->star_center.y;
	}
	if ( cs->orient==mo_rotateto ||
		cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
	    if ( cs->degrees!=DEGREES_END )
		cs->degrees=180-cs->degrees;
	    if ( cs->orient==mo_cw_rotateto )
		cs->orient = mo_ccw_rotateto;
	    else if ( cs->orient==mo_ccw_rotateto )
		cs->orient = mo_cw_rotateto;
	} else if ( cs->orient==mo_rotating )
	    cs->degrees = - cs->degrees;
	else if ( cs->orient==mo_forward_90 )
	    cs->orient = mo_forward90;
	else if ( cs->orient==mo_forward90 )
	    cs->orient = mo_forward_90;
	/* Technically we should mirror the hand clasps too */
	/* but that just seems weird to a user who wants to adjust them */
    }

    if ( trans->dohflip ) {
	if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
	    cs->endPoint.x = trans->flip_xcenter- cs->endPoint.x;
	    if ( cs->cmd==c_curveto ) {
		cs->cp1.x = trans->flip_xcenter- cs->cp1.x;
		cs->cp2.x = trans->flip_xcenter- cs->cp2.x;
	    }
	}
	if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
	    cs->star_center.x = trans->flip_xcenter- cs->star_center.x;
	}
	if ( cs->orient==mo_rotateto ||
		cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
	    if ( cs->degrees!=DEGREES_END )
		cs->degrees=360-cs->degrees;
	    if ( cs->orient==mo_cw_rotateto )
		cs->orient = mo_ccw_rotateto;
	    else if ( cs->orient==mo_ccw_rotateto )
		cs->orient = mo_cw_rotateto;
	} else if ( cs->orient==mo_rotating )
	    cs->degrees = - cs->degrees;
	else if ( cs->orient==mo_forward_90 )
	    cs->orient = mo_forward90;
	else if ( cs->orient==mo_forward90 )
	    cs->orient = mo_forward_90;
	/* Technically we should mirror the hand clasps too */
	/* but that just seems weird to a user who wants to adjust them */
    }

    if ( trans->xscale!=1 || trans->yscale!=1 ) {
	if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
	    x = trans->xcenter_new + round(trans->xscale*(cs->endPoint.x-trans->xcenter_old));
	    y = trans->ycenter_new + round(trans->yscale*(cs->endPoint.y-trans->ycenter_old));
	    cs->endPoint.x = x; cs->endPoint.y = y;
	    if ( cs->cmd==c_curveto ) {
		x = trans->xcenter_new + round(trans->xscale*(cs->cp1.x-trans->xcenter_old));
		y = trans->ycenter_new + round(trans->yscale*(cs->cp1.y-trans->ycenter_old));
		cs->cp1.x = x; cs->cp1.y = y;
		x = trans->xcenter_new + round(trans->xscale*(cs->cp2.x-trans->xcenter_old));
		y = trans->ycenter_new + round(trans->yscale*(cs->cp2.y-trans->ycenter_old));
		cs->cp2.x = x; cs->cp2.y = y;
	    }
	}
	if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
	    x = trans->xcenter_new + round(trans->xscale*(cs->star_center.x-trans->xcenter_old));
	    y = trans->ycenter_new + round(trans->yscale*(cs->star_center.y-trans->ycenter_old));
	    cs->star_center.x = x; cs->star_center.y = y;
	}
	if ( cs->cmd==c_moveto || cs->orient==mo_rotateto ||
		cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
	    /* If we don't scale x/y equally then angles will change */
	    /* If we scale by -1 angles will also change */
	    /* At the moment I don't care, but this is a latent bug!!!!! */
	}
    }

    if ( trans->timescale>0 && trans->timescale!=1 ) {
	ValChange *vc;
	RThetaChange *pc;
	cs->millibars *= trans->timescale;
	if ( cs->drop_right_millibars!=HAND_MILLIBARS_UNSET )
	    cs->drop_right_millibars *= trans->timescale;
	if ( cs->drop_left_millibars!=HAND_MILLIBARS_UNSET )
	    cs->drop_left_millibars *= trans->timescale;
	if ( cs->join_right_millibars!=HAND_MILLIBARS_UNSET )
	    cs->join_right_millibars *= trans->timescale;
	if ( cs->join_left_millibars!=HAND_MILLIBARS_UNSET )
	    cs->join_left_millibars *= trans->timescale;
	cs->start_honour *= trans->timescale;
	cs->end_honour *= trans->timescale;
	for ( vc=cs->outness; vc!=NULL && vc->millibars!=-1; ++vc )
	    vc->millibars *= trans->timescale;
	for ( vc=cs->levitation; vc!=NULL && vc->millibars!=-1; ++vc )
	    vc->millibars *= trans->timescale;
	for ( pc=cs->rightHand; pc!=NULL && pc->millibars!=-1; ++pc )
	    pc->millibars *= trans->timescale;
	for ( pc=cs->leftHand; pc!=NULL && pc->millibars!=-1; ++pc )
	    pc->millibars *= trans->timescale;
	for ( pc=cs->swordPoint; pc!=NULL && pc->millibars!=-1; ++pc )
	    pc->millibars *= trans->timescale;
    }


	if ( trans->unmapped_hands!=0 ) {
	    int h;
	    /* I'm going to leave extranious drop hands because they won't cause */
	    /*  problems and are too hard to figure out anyway */
	    if ( cs->join_right_hand!=0 /* && (cs->join_right_hand&HAND_MINORSET_OFFSET)==0*/ ) {
		h = cs->join_right_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR && ((1L<<h) & trans->unmapped_hands) ) {
		    cs->join_right_hand = 0;
		    cs->join_right_millibars = HAND_MILLIBARS_UNSET;
		}
	    }
	    if ( cs->join_left_hand!=0 /* && (cs->join_left_hand&HAND_MINORSET_OFFSET)==0*/ ) {
		h = cs->join_left_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR && ((1L<<h) & trans->unmapped_hands) ) {
		    cs->join_left_hand = 0;
		    cs->join_left_millibars = HAND_MILLIBARS_UNSET;
		}
	    }
	}

	if ( trans->full_remap ) {
	    int h;
	    if ( cs->join_right_hand!=0 /* && (cs->join_right_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_right_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR ) {
		    if ( trans->handmap[h]==-1 ) {
			cs->join_right_millibars = HAND_MILLIBARS_UNSET;
			cs->join_right_hand = 0;
		    } else
			cs->join_right_hand = trans->handmap[h] |
				(cs->join_right_hand & ~HAND_INDIVIDUAL_MASK);
		}
	    }
	    if ( cs->join_left_hand!=0 /* && (cs->join_left_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_left_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR ) {
		    if ( trans->handmap[h]==-1 ) {
			cs->join_left_millibars = HAND_MILLIBARS_UNSET;
			cs->join_left_hand = 0;
		    } else
			cs->join_left_hand = trans->handmap[h] |
				(cs->join_left_hand & ~HAND_INDIVIDUAL_MASK);
		}
	    }
	    if ( cs->orient==mo_faceDancer )
		cs->faceDancer = trans->handmap[cs->faceDancer];
	    if ( cs->join_left_to_point != -1 )
		cs->join_left_to_point = trans->handmap[cs->join_left_to_point];
	}
	if ( trans->hand_offset!=0 ) {
	    int h;
	    /* In Vic Cedar's contra I need clasps in different minor sets */
	    /*  adjusted too */
	    if ( cs->join_right_hand!=0 /* && (cs->join_right_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_right_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR )
		    cs->join_right_hand = ( (h+trans->hand_offset) % trans->hand_mod ) |
			    (cs->join_right_hand & ~HAND_INDIVIDUAL_MASK);
	    }
	    if ( cs->join_left_hand!=0 /* && (cs->join_left_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_left_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR )
		    cs->join_left_hand = ( (h+trans->hand_offset) % trans->hand_mod ) |
			    (cs->join_left_hand & ~HAND_INDIVIDUAL_MASK);
	    }
	    if ( cs->join_left_to_point!=-1 )
		cs->join_left_to_point = (cs->join_left_to_point+trans->hand_offset) % trans->hand_mod;
	    if ( cs->orient==mo_faceDancer )
		cs->faceDancer = (cs->faceDancer+trans->hand_offset) % trans->hand_mod;
	}

#ifdef DEBUG
    if ( trans->repeat!=0 && trans->repeat!=1 ) {
	fprintf( stderr, "%d: repeat argument only supported on blocks.\n", cs->lineno );
    }
#endif
}

static void conditionTransform(Condition *cond, struct transformation *trans) {

    if ( cond == NULL )
return;
    if ( trans->rotateby!=180 && !trans->dohflip && !trans->dovflip )
return;

    switch ( cond->operator ) {
      case op_and:
      case op_or:
      case op_not:
	conditionTransform(cond->op1,trans);
	conditionTransform(cond->op2,trans);
      break;
      case op_topset:
        if ( trans->rotateby==180 || trans->dohflip )
	    cond->operator = op_bottomset;
      break;
      case op_bottomset:
        if ( trans->rotateby==180 || trans->dohflip )
	    cond->operator = op_topset;
      break;
      case op_majorpos:
        /* top couple==0 should become bottom couple==-1 */
	/* second couple==1 should become next to bottom==-2 */
	/* and vice versa */
        if ( trans->rotateby==180 || trans->dohflip )
	    cond->argument1 = -1 - cond->argument1;
      break;
      case op_proper:
        if ( trans->rotateby==180 || trans->dovflip )
	    cond->operator = op_improper;
      break;
      case op_improper:
        if ( trans->rotateby==180 || trans->dovflip )
	    cond->operator = op_proper;
      break;
      case op_part: case op_last: case op_first:
      break;
    }
}

void danceBlockTransform(Block *b,struct transformation *trans) {
    int i;
    double c=1, s=0;
    double x,y;
    int clasp;
    double slide_base_x=0, slide_base_y=0;
    int slide_millibars=0;
    int slide_millibars_sofar=0;
    int total_time_offset=0, time_adjustment=0;
    double timescale=trans->timescale;
    
    if ( trans->rotateby!=0 ) {
	c = cos(trans->rotateby*PI/180);
	s = sin(trans->rotateby*PI/180);
    }
    if ( trans->x_slide!=0 || trans->y_slide!=0 ) {
	slide_millibars=0;
	for ( i=0; i<b->move_cnt; ++i ) {
	    FigureSegment *cs = &b->moves[i];
	    if ( cs->cmd==c_if || cs->cmd==c_transform ) {
		fprintf( stderr, ":%ld Block within a slide transform. I do not deal with that.\n", b->lineno );
exit(1);
	    }
	    if ( cs->cmd!=c_lineto && cs->cmd!=c_curveto )
	continue;
	    slide_millibars += cs->millibars;
	}
    }
    timescale=trans->timescale;
    if ( trans->timescale>0 && trans->timescale!=1 ) {
	int mb=0, mbs=0, cnt=0, off;

	for ( i=0; i<b->move_cnt; ++i ) {
	    FigureSegment *cs = &b->moves[i];
	    if ( cs->millibars!=0 ) {
		mb += cs->millibars;
		mbs += round( cs->millibars * trans->timescale );
		++cnt;
	    }
	}
	if ( mb==0 || cnt==0 ) {
	    fprintf( stderr, ":%ld No figure segments in a time-scaled block. I do not deal with that.\n", b->lineno );
return;
	}
	/* Rounding error fixup */
	off = 250*round(mb * trans->timescale / 250.0 ) - (mb*trans->timescale);
	if ( mb%250 == 0 && off>-2*cnt && off < 2*cnt )
	    mb = 250*round(mb * trans->timescale / 250.0 );	/* Round to the nearest 1/4 second */
	else
	    mb = round( mb*trans->timescale );
	total_time_offset = mb - mbs;
	if ( total_time_offset>0 )
	    time_adjustment = (total_time_offset+cnt-1)/cnt;
	else {
	    time_adjustment = (-total_time_offset+cnt-1)/cnt;
	    time_adjustment = - time_adjustment;
	}
    } else if ( trans->barscale!=0 ) {
	int mb=0, mbs=0, cnt=0;

	for ( i=0; i<b->move_cnt; ++i ) {
	    FigureSegment *cs = &b->moves[i];
	    if ( cs->cmd==c_if )
		fprintf( stderr, ":%ld if command in a bar-scaled block. I do not deal with that.\n", b->lineno );
	    if ( cs->millibars!=0 ) {
		mb += cs->millibars;
		++cnt;
	    }
	}
	if ( mb==0 || cnt==0 ) {
	    fprintf( stderr, ":%ld No figure segments in a bar-scaled block. I do not deal with that.\n", b->lineno );
exit(1);
	}
	timescale = (double) trans->barscale / (double) mb;

	for ( i=0; i<b->move_cnt; ++i ) {
	    FigureSegment *cs = &b->moves[i];
	    if ( cs->millibars!=0 ) {
		mbs += round( cs->millibars * timescale );
	    }
	}
	total_time_offset = trans->barscale - mbs;
	if ( total_time_offset>0 )
	    time_adjustment = (total_time_offset+cnt-1)/cnt;
	else {
	    time_adjustment = (-total_time_offset+cnt-1)/cnt;
	    time_adjustment = - time_adjustment;
	}
    }

    for ( i=0; i<b->move_cnt; ++i ) {
	FigureSegment *cs = &b->moves[i];

	if ( cs->cmd == c_if ) {
	    Block *b;
	    for ( b = cs->ifblock; b!=NULL; b=b->next ) {
		conditionTransform(b->condition,trans);
		danceBlockTransform(b,trans);
	    }
    continue;
	}

	if ( trans->xoff!=0 || trans->yoff!=0 ) {
	    if ( (cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) && !cs->position_absolute ) {
		cs->endPoint.x += trans->xoff;
		cs->endPoint.y += trans->yoff;
		if ( cs->cmd==c_curveto ) {
		    cs->cp1.x += trans->xoff;
		    cs->cp1.y += trans->yoff;
		    cs->cp2.x += trans->xoff;
		    cs->cp2.y += trans->yoff;
		}
	    }
	    if ( (cs->join_right_hand&HAND_INDIVIDUAL_MASK)==HAND_STAR ||
		    (cs->join_left_hand&HAND_INDIVIDUAL_MASK)==HAND_STAR ) {
		cs->star_center.x += trans->xoff;
		cs->star_center.y += trans->yoff;
	    }
	}

	if ( trans->x_slide!=0 || trans->y_slide!=0 ) if ( cs->cmd==c_lineto || cs->cmd==c_curveto ) {
	    if ( cs->cmd==c_curveto ) {
		cs->cp1.x += slide_base_x;
		cs->cp1.y += slide_base_y;
	    }
	    slide_millibars_sofar += cs->millibars;
	    slide_base_x = slide_millibars_sofar*trans->x_slide / slide_millibars;
	    slide_base_y = slide_millibars_sofar*trans->y_slide / slide_millibars;
	    if ( cs->cmd==c_curveto ) {
		cs->cp2.x += slide_base_x;
		cs->cp2.y += slide_base_y;
	    }
	    cs->endPoint.x += slide_base_x;
	    cs->endPoint.y += slide_base_y;
	    if ( (cs->join_right_hand&HAND_INDIVIDUAL_MASK)==HAND_STAR ||
		    (cs->join_left_hand&HAND_INDIVIDUAL_MASK)==HAND_STAR ) {
		cs->star_center.x += slide_base_x;
		cs->star_center.y += slide_base_y;
	    }
	}
	
	if ( trans->xscale!=1 || trans->yscale!=1 ) {
		if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
		    x = trans->xcenter_new + round(trans->xscale*(cs->endPoint.x-trans->xcenter_old));
		    y = trans->ycenter_new + round(trans->yscale*(cs->endPoint.y-trans->ycenter_old));
		    cs->endPoint.x = x; cs->endPoint.y = y;
		    if ( cs->cmd==c_curveto ) {
			x = trans->xcenter_new + round(trans->xscale*(cs->cp1.x-trans->xcenter_old));
			y = trans->ycenter_new + round(trans->yscale*(cs->cp1.y-trans->ycenter_old));
			cs->cp1.x = x; cs->cp1.y = y;
			x = trans->xcenter_new + round(trans->xscale*(cs->cp2.x-trans->xcenter_old));
			y = trans->ycenter_new + round(trans->yscale*(cs->cp2.y-trans->ycenter_old));
			cs->cp2.x = x; cs->cp2.y = y;
		    }
		}
		if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
		    x = trans->xcenter_new + round(trans->xscale*(cs->star_center.x-trans->xcenter_old));
		    y = trans->ycenter_new + round(trans->yscale*(cs->star_center.y-trans->ycenter_old));
		    cs->star_center.x = x; cs->star_center.y = y;
		}
		if ( cs->cmd==c_moveto || cs->orient==mo_rotateto ||
			cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
		    /* If we don't scale x/y equally then angles will change */
		    /* If we scale by -1 angles will also change */
		    /* At the moment I don't care, but this is a latent bug!!!!! */
		}
	}

	if ( trans->rotateby!=0 ) {
	    if ( (cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) && !cs->position_absolute ) {
		double x = trans->center_x + c*(cs->endPoint.x-trans->center_x) - s*(cs->endPoint.y-trans->center_y);
		double y = trans->center_y + s*(cs->endPoint.x-trans->center_x) + c*(cs->endPoint.y-trans->center_y);
		cs->endPoint.x = myround(x,cs->endPoint.x,trans->rotateby,trans->center_x); cs->endPoint.y = myround(y,cs->endPoint.y,trans->rotateby,trans->center_y);
		if ( cs->cmd==c_curveto ) {
		    x = trans->center_x + c*(cs->cp1.x-trans->center_x) - s*(cs->cp1.y-trans->center_y);
		    y = trans->center_y + s*(cs->cp1.x-trans->center_x) + c*(cs->cp1.y-trans->center_y);
		    cs->cp1.x = myround(x,cs->cp1.x,trans->rotateby,trans->center_x); cs->cp1.y = myround(y,cs->cp1.y,trans->rotateby,trans->center_y);
		    x = trans->center_x + c*(cs->cp2.x-trans->center_x) - s*(cs->cp2.y-trans->center_y);
		    y = trans->center_y + s*(cs->cp2.x-trans->center_x) + c*(cs->cp2.y-trans->center_y);
		    cs->cp2.x = myround(x,cs->cp2.x,trans->rotateby,trans->center_x); cs->cp2.y = myround(y,cs->cp2.y,trans->rotateby,trans->center_y);
		}
	    }
	    if ( cs->cmd==c_moveto || cs->orient==mo_rotateto ||
		    cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
		if ( cs->degrees!=DEGREES_END ) {
		    cs->degrees += trans->rotateby;
		    if ( cs->degrees>=360 )
			cs->degrees -= 360;
		}
	    }
	    if ( (cs->join_right_hand&HAND_INDIVIDUAL_MASK)==HAND_STAR ||
		    (cs->join_left_hand&HAND_INDIVIDUAL_MASK)==HAND_STAR ) {
		double x = trans->center_x + c*(cs->star_center.x-trans->center_x) - s*(cs->star_center.y-trans->center_y);
		double y = trans->center_y + s*(cs->star_center.x-trans->center_x) + c*(cs->star_center.y-trans->center_y);
		cs->star_center.x = round(x);
		cs->star_center.y = round(y);
	    }
	    if ( trans->rotateby==180 ) {
		/* If we rotate by 180 then any hand clasp that used to refer */
		/*  to the previous set will now refer to the subsequent set */
		/*  and vice versa. */
		if ( cs->join_right_hand&HAND_MINORSET_MASK )
		    cs->join_right_hand ^= HAND_MINORSET_UP;
		if ( cs->join_left_hand&HAND_MINORSET_MASK )
		    cs->join_left_hand ^= HAND_MINORSET_UP;
		/* Similarly any condition which refered to the top set */
		/*  now refers to the bottom and vice versa */
		switch ( cs->condition ) {
		  case if_always:
		  break;
		  case if_topset:
		    cs->condition = if_bottomset;
		  break;
		  case if_nottopset:
		    cs->condition = if_notbottomset;
		  break;
		  case if_bottomset:
		    cs->condition = if_topset;
		  break;
		  case if_notbottomset:
		    cs->condition = if_nottopset;
		  break;
		  
		  case if_semitopset:
		    cs->condition = if_semibottomset;
		  break;
		  case if_notsemitopset:
		    cs->condition = if_notsemibottomset;
		  break;
		  case if_semibottomset:
		    cs->condition = if_semitopset;
		  break;
		  case if_notsemibottomset:
		    cs->condition = if_notsemitopset;
		  break;

		  case if_nottopsetorsemi:
		    cs->condition = if_notbottomsetorsemi;
		  break;
		  case if_notbottomsetorsemi:
		    cs->condition = if_nottopsetorsemi;
		  break;

		  case if_nottopbottomset:
		    cs->condition = if_nottopbottomset;
		  break;
		}
	    }	    
	}

	if ( trans->dohflip ) {
	    if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
		cs->endPoint.x = trans->flip_xcenter- cs->endPoint.x;
		if ( cs->cmd==c_curveto ) {
		    cs->cp1.x = trans->flip_xcenter- cs->cp1.x;
		    cs->cp2.x = trans->flip_xcenter- cs->cp2.x;
		}
	    }
	    if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
		cs->star_center.x = trans->flip_xcenter- cs->star_center.x;
	    }
	    if ( cs->orient==mo_rotateto ||
		    cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
		if ( cs->degrees!=DEGREES_END ) {
		    cs->degrees = 360 - cs->degrees;
		    if ( cs->degrees >= 360 )
			cs->degrees -= 360;
		}
		if ( cs->orient==mo_cw_rotateto )
		    cs->orient = mo_ccw_rotateto;
		else if ( cs->orient==mo_ccw_rotateto )
		    cs->orient = mo_cw_rotateto;
	    } else if ( cs->orient==mo_rotating )
		cs->degrees = - cs->degrees;
	    else if ( cs->orient==mo_forward_90 )
		cs->orient = mo_forward90;
	    else if ( cs->orient==mo_forward90 )
		cs->orient = mo_forward_90;
#if 1
	    clasp = cs->join_right_hand;
	    if ( clasp != 0 )
		clasp ^= (HAND_RIGHT_MASK|HAND_LEFT_MASK);
	    if ( (clasp&HAND_MINORSET_MASK)!=0 )
		clasp ^= HAND_MINORSET_UP;
	    if ( cs->join_left_hand != 0 )
		cs->join_left_hand ^= (HAND_RIGHT_MASK|HAND_LEFT_MASK);
	    if ( (cs->join_left_hand&HAND_MINORSET_MASK)!=0 )
		cs->join_left_hand ^= HAND_MINORSET_UP;
	    cs->join_right_hand = cs->join_left_hand;
	    cs->join_left_hand = clasp;
	    clasp = cs->drop_right_millibars;
	    cs->drop_right_millibars = cs->drop_left_millibars;
	    cs->drop_left_millibars = clasp;
	    clasp = cs->join_right_millibars;
	    cs->join_right_millibars = cs->join_left_millibars;
	    cs->join_left_millibars = clasp;
#endif
	}

	if ( trans->dovflip ) {
	    if ( cs->cmd==c_moveto || cs->cmd==c_lineto || cs->cmd==c_curveto ) {
		cs->endPoint.y = trans->flip_ycenter- cs->endPoint.y;
		if ( cs->cmd==c_curveto ) {
		    cs->cp1.y = trans->flip_ycenter- cs->cp1.y;
		    cs->cp2.y = trans->flip_ycenter- cs->cp2.y;
		}
	    }
	    if ( cs->star_center.x!=0 || cs->star_center.y!=0 ) {
		cs->star_center.y = trans->flip_ycenter- cs->star_center.y;
	    }
	    if ( cs->orient==mo_rotateto ||
		    cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
		if ( cs->degrees!=DEGREES_END ) {
		    cs->degrees = 180 - cs->degrees;
		    if ( cs->degrees >= 360 )
			cs->degrees -= 360;
		}
		if ( cs->orient==mo_cw_rotateto )
		    cs->orient = mo_ccw_rotateto;
		else if ( cs->orient==mo_ccw_rotateto )
		    cs->orient = mo_cw_rotateto;
	    } else if ( cs->orient==mo_rotating )
		cs->degrees = - cs->degrees;
	    else if ( cs->orient==mo_forward_90 )
		cs->orient = mo_forward90;
	    else if ( cs->orient==mo_forward90 )
		cs->orient = mo_forward_90;
#if 1
	    clasp = cs->join_right_hand;
	    if ( clasp != 0 )
		clasp ^= (HAND_RIGHT_MASK|HAND_LEFT_MASK);
	    if ( cs->join_left_hand != 0 )
		cs->join_left_hand ^= (HAND_RIGHT_MASK|HAND_LEFT_MASK);
	    cs->join_right_hand = cs->join_left_hand;
	    cs->join_left_hand = clasp;
	    clasp = cs->drop_right_millibars;
	    cs->drop_right_millibars = cs->drop_left_millibars;
	    cs->drop_left_millibars = clasp;
	    clasp = cs->join_right_millibars;
	    cs->join_right_millibars = cs->join_left_millibars;
	    cs->join_left_millibars = clasp;
#endif
	}

	if ( timescale>0 && timescale!=1 ) {
	    ValChange *vc;
	    RThetaChange *pc;
	    int toff=0;
	    if ( cs->millibars!=0 ) {
		toff = time_adjustment;
		if (( time_adjustment>0 && toff>total_time_offset ) ||
			 ( time_adjustment<0 && toff<total_time_offset ))
		    toff = total_time_offset;
		total_time_offset -= toff;
	    }

	    cs->millibars = round( cs->millibars * timescale) + toff;
/* I exclude 0 because a) if I have a negative time_adjustment, then I get a large positive number, and b) because if it's 0, it should stay 0 */
	    if ( cs->drop_right_millibars!=HAND_MILLIBARS_UNSET && cs->drop_right_millibars!=0 )
		cs->drop_right_millibars = cs->drop_right_millibars*timescale+toff;
	    if ( cs->drop_left_millibars!=HAND_MILLIBARS_UNSET && cs->drop_left_millibars!=0 )
		cs->drop_left_millibars = cs->drop_left_millibars*timescale+toff;
	    if ( cs->join_right_millibars!=HAND_MILLIBARS_UNSET && cs->join_right_millibars !=0 )
		cs->join_right_millibars = cs->join_right_millibars*timescale+toff;
	    if ( cs->join_left_millibars!=HAND_MILLIBARS_UNSET && cs->join_left_millibars !=0 )
		cs->join_left_millibars = cs->join_left_millibars*timescale+toff;
	    cs->start_honour = cs->start_honour*timescale+toff;
	    cs->end_honour = cs->end_honour*timescale+toff;
	    for ( vc=cs->outness; vc!=NULL && vc->millibars!=-1; ++vc )
		if ( vc->millibars!=0 )
		    vc->millibars = vc->millibars*timescale+toff;
	    for ( vc=cs->levitation; vc!=NULL && vc->millibars!=-1; ++vc )
		if ( vc->millibars!=0 )
		    vc->millibars = vc->millibars*timescale+toff;
	    for ( pc=cs->rightHand; pc!=NULL && pc->millibars!=-1; ++pc )
		if ( pc->millibars!=0 )
		    pc->millibars = pc->millibars*timescale+toff;
	    for ( pc=cs->leftHand; pc!=NULL && pc->millibars!=-1; ++pc )
		if ( pc->millibars!=0 )
		    pc->millibars = pc->millibars*timescale+toff;
	    for ( pc=cs->swordPoint; pc!=NULL && pc->millibars!=-1; ++pc )
		if ( pc->millibars!=0 )
		    pc->millibars = pc->millibars*timescale+toff;
	}


	if ( trans->unmapped_hands!=0 ) {
	    int h;
	    /* I'm going to leave extranious drop hands because they won't cause */
	    /*  problems and are too hard to figure out anyway */
	    if ( cs->join_right_hand!=0 /* && (cs->join_right_hand&HAND_MINORSET_OFFSET)==0*/ ) {
		h = cs->join_right_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR && ((1L<<h) & trans->unmapped_hands) ) {
		    cs->join_right_hand = 0;
		    cs->join_right_millibars = HAND_MILLIBARS_UNSET;
		}
	    }
	    if ( cs->join_left_hand!=0 /* && (cs->join_left_hand&HAND_MINORSET_OFFSET)==0*/ ) {
		h = cs->join_left_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR && ((1L<<h) & trans->unmapped_hands) ) {
		    cs->join_left_hand = 0;
		    cs->join_left_millibars = HAND_MILLIBARS_UNSET;
		}
	    }
	}

	if ( trans->full_remap ) {
	    int h;
	    if ( cs->join_right_hand!=0 /* && (cs->join_right_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_right_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR ) {
		    if ( trans->handmap[h]==-1 ) {
			cs->join_right_millibars = HAND_MILLIBARS_UNSET;
			cs->join_right_hand = 0;
		    } else
			cs->join_right_hand = trans->handmap[h] |
				(cs->join_right_hand & ~HAND_INDIVIDUAL_MASK);
		}
	    }
	    if ( cs->join_left_hand!=0 /* && (cs->join_left_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_left_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR ) {
		    if ( trans->handmap[h]==-1 ) {
			cs->join_left_millibars = HAND_MILLIBARS_UNSET;
			cs->join_left_hand = 0;
		    } else
			cs->join_left_hand = trans->handmap[h] |
				(cs->join_left_hand & ~HAND_INDIVIDUAL_MASK);
		}
	    }
	    if ( cs->orient==mo_faceDancer )
		cs->faceDancer = trans->handmap[cs->faceDancer];
	    if ( cs->join_left_to_point != -1 )
		cs->join_left_to_point = trans->handmap[cs->join_left_to_point];
	}
	if ( trans->hand_offset!=0 ) {
	    int h;
	    if ( cs->join_right_hand!=0 /* && (cs->join_right_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_right_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR )
		    cs->join_right_hand = ( (h+trans->hand_offset) % trans->hand_mod ) |
			    (cs->join_right_hand & ~HAND_INDIVIDUAL_MASK);
	    }
	    if ( cs->join_left_hand!=0 /* && (cs->join_left_hand&HAND_MINORSET_OFFSET)==0 */ ) {
		h = cs->join_left_hand & HAND_INDIVIDUAL_MASK;
		if ( h!=HAND_STAR )
		    cs->join_left_hand = ( (h+trans->hand_offset) % trans->hand_mod ) |
			    (cs->join_left_hand & ~HAND_INDIVIDUAL_MASK);
	    }
	    if ( cs->join_left_to_point!=-1 )
		cs->join_left_to_point = (cs->join_left_to_point+trans->hand_offset) % trans->hand_mod;
	    if ( cs->orient==mo_faceDancer )
		cs->faceDancer = (cs->faceDancer+trans->hand_offset) % trans->hand_mod;
	}
    }

    if ( trans->repeat>1 ) {
	int r;
	if ( trans->repeat*b->move_cnt > b->move_max ) {
	    b->moves = realloc(b->moves,(b->move_max = trans->repeat*b->move_cnt+1)*sizeof(FigureSegment));
	}

	for ( r=1; r<trans->repeat; ++r ) {
	    Block *newb = blockCopy(b);
	    memcpy(b->moves+r*b->move_cnt,newb->moves,b->move_cnt*sizeof(FigureSegment));
	    free(newb->moves);
	    free(newb);
	}
	b->move_cnt *= trans->repeat;
    }
}

static int danceParseRoleCopy(const char *filename,xmlNodePtr copyCmdNode, Person *ret, Block *tob, Dance *dance ) {
    enum role role;
    enum sex sex = ret->sex;
    struct transformation trans;
    Person *source;
    int i,j;
    xmlChar *temp;
    Block *b;
    int barStart, barEnd, barSofar;
    /* In a triple minor when 2 couples are out at the bottom we want to copy */
    /*  the behavior of the first two couples of the minorset, but there will */
    /*  be no third couple. They dance with a ghost and never clasp hands with*/
    /*  the ghosts */
    /* In a circular set, on the other hand, everyone does the same thing and */
    /*  hand clasps should wrap. */

    /* Sex is not required, we can assume we are copying a role of the same sex */
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "sex");
    if ( temp!=NULL ) {
	if ( *temp=='x' || *temp=='X' )
	    sex = ret->sex==s_male ? s_female : s_male;
	else if ( *temp=='m' || *temp=='M' )
	    sex = s_male;
	else if ( *temp=='f' || *temp=='F' )
	    sex = s_female;
	else if ( *temp=='l' || *temp=='L' )
	    sex = s_left;
	else if ( *temp=='c' || *temp=='C' )
	    sex = s_center;
	else if ( *temp=='r' || *temp=='R' )
	    sex = s_right;
	else
	    fprintf(stderr,"In file %s:%ld, bad sex %s in copy command.", filename, xmlGetLineNo(copyCmdNode), temp );
	xmlFree(temp);
    }
    /* Role is required */
    temp = xmlGetProp(copyCmdNode,(xmlChar *) "role");
    if ( temp!=NULL ) {
	if ( strcasecmp((const char *) temp,"me")==0 || strcasecmp((const char *) temp,"mine")==0 )
	    role = ret->effective_role;
        else
	    role = danceMatchEnum(roleNames,(char *) temp);
	if ( (int) (role)==-1 ) {
	    setError("In file %s:%ld, bad role %s in copy command.", filename, xmlGetLineNo(copyCmdNode), temp );
	    xmlFree(temp);
return( false );
	}
	xmlFree(temp);
    } else {
	setError("In file %s:%ld, no role specified in copy command.", filename, xmlGetLineNo(copyCmdNode) );
return( false );
    }

    /* transformations are optional */
    danceParseTransformation(filename, copyCmdNode,&trans);

    source = NULL;
    for ( i=0; i<dance->role_cnt; ++i ) {
	Person *test = &dance->roles[i];
	if ( test->effective_role==role && test->sex==sex ) {
	    source = test;
    break;
	}
    }
    if ( source==NULL ) {
	setError("In file %s:%ld, failed to find role=%s,sex=%d in copy command. (perhaps you are trying to copy a role defined later in the file?)", filename, xmlGetLineNo(copyCmdNode), roleNames[role], sex );
return( false );
    }

    temp = xmlGetProp(copyCmdNode,(xmlChar *) "end-bar");
    /* A copy without an end-bar copies the entire role. One with one copies  */
    /*  the moves within the time range. If the role being copied doesn't start*/
    /*  or end exactly, that's an error -- except if a role has fewer bars than*/
    /*  specified give that a warning (that's for use when debugging) */
    /* Well, that's what should happen. Easier not to */
    if ( temp!=NULL ) {
	char *end, *end2;
	Block tempb, *tempCopy;

	memset(&tempb,0,sizeof(tempb));

	if ( *temp=='+' )
	    barEnd = round( MILLIBARS_MUL * strtod((char *) (temp+1),&end)) + blockCountMillibars(&ret->figures);
	else
	    barEnd = round( MILLIBARS_MUL * strtod((char *) temp,&end));
	temp = xmlGetProp(copyCmdNode,(xmlChar *) "start-bar");
	if ( temp!=NULL ) {
	    if ( *temp=='+' )
		barStart = round( MILLIBARS_MUL * strtod((char *) temp+1,&end2))+
		    blockCountMillibars(&ret->figures);
	    else
		barStart = round( MILLIBARS_MUL * strtod((char *) temp,&end2));
	    if ( barStart<0 || *end2!='\0' ) {
		setError("In file %s:%ld, bad 'start-bar' specified in copy command (%s).", filename, xmlGetLineNo(copyCmdNode), (char *) xmlGetProp(copyCmdNode,(xmlChar *) "end-bar") );
return( false );
	    }
	} else
	    barStart = blockCountMillibars(&ret->figures);
	if ( barEnd<=barStart || *end!='\0' ) {
	    setError("In file %s:%ld, 'start-bar' too big (or erroneous) in copy command (%d).", filename, xmlGetLineNo(copyCmdNode), barStart );
return( false );
	}

	barSofar = 0;
	for ( i=0; i<source->figures.move_cnt; ++i ) {
	    FigureSegment *from = &source->figures.moves[i];
	    FigureSegment *to;

	    if ( barSofar>=barEnd )
	break;
	    if ( barSofar<barStart )
		/* Do Nothing */;
	    else if ( barSofar==0 && from->cmd==c_moveto && (tob->move_cnt>0 || tob!=&ret->figures) )
		/* Only copy the initial move to if the to block is empty */;
	    else {

		if ( tempb.move_cnt>=tempb.move_max ) {
		    tempb.moves = realloc(tempb.moves,(tempb.move_max+=15)*sizeof(FigureSegment));
		    memset(tempb.moves+tempb.move_cnt,0,(tempb.move_max-tempb.move_cnt)*sizeof(FigureSegment));
		}
		to = &tempb.moves[tempb.move_cnt++];
		*to = *from;
	    }
	    if ( from->cmd==c_if )
		barSofar += blockCountMillibars(from->ifblock);
	    else
		barSofar += from->millibars;
	}

	tempCopy = blockCopy(&tempb);
	danceBlockTransform(tempCopy,&trans);
#ifdef DEBUG
	{ int i; short lineno = xmlGetLineNo(copyCmdNode);
	for ( i=0; i<tempCopy->move_cnt; ++i ) {
	    FigureSegment *cs = &tempCopy->moves[i];
	    cs->copy_lineno = lineno;

	}
	}
#endif

	if ( tob->move_cnt+tempCopy->move_cnt > tob->move_max ) {
	    tob->moves = realloc(tob->moves,(tob->move_max+=tempCopy->move_cnt+5)*sizeof(FigureSegment));
	    memset(tob->moves+tob->move_cnt,0,(tob->move_max-tob->move_cnt)*sizeof(FigureSegment));
	}
	for ( i=0, j=tob->move_cnt; i<tempCopy->move_cnt; ++i,++j ) {
	    FigureSegment *from = &tempCopy->moves[i];
	    FigureSegment *to   = &tob->moves[j];
	    *to = *from;
	}
	tob->move_cnt = j;
	free(tempCopy->moves);
	free(tempCopy);
	free(tempb.moves);
    } else {
	if ( role==ret->effective_role && sex==ret->sex ) {
	    setError("In file %s:%ld, bad role %s in copy command (can't copy one's self).", filename, xmlGetLineNo(copyCmdNode), temp );
return( false );
	}
	free( ret->figures.moves );
	b = blockCopy(&source->figures);
	memcpy(&ret->figures,b,sizeof(Block));
	free(b);

	danceBlockTransform(&ret->figures,&trans);
    }

    /* Mmm. No second gong for round the room dances (sicillian) */
    if ( dance->style==ds_scottish && dance->improper_mask&(1<<((int) ret->effective_role)) &&
	    dance->formation!=sp_dupleMinor && dance->formation!=sp_fourFaceFour &&
	    ret->figures.move_cnt>=1 && ret->figures.moves[0].cmd==c_moveto ) {
	/* Scottish puts people improper after at the second gong */
	if ( ret->sex==s_male && ret->figures.moves[0].endPoint.y>192 ) {
	    ret->figures.moves[0].endPoint.y = 64;
	    ret->figures.moves[0].degrees = 0;
	} else if ( ret->sex==s_female && ret->figures.moves[0].endPoint.y<192 ) {
	    ret->figures.moves[0].endPoint.y = 320;
	    ret->figures.moves[0].degrees = 180;
	}
    }
	
return( true );
}

static unsigned int parsePartMask(const char *filename,Figure *ret, char *temp, xmlNodePtr node) {
    unsigned int mask=0;
    char *end, *pt;

    for ( pt=temp; *pt!='\0'; pt = end ) {
	int val = strtol(pt,&end,10);
	if ( end==pt || val<=0 || val>32 || val>ret->part_cnt || (mask&(1<<(val-1)))!=0 ) {
	    setError("In figure %s in file %s:%ld, bad part list: \"%s\".", ret->name, filename, xmlGetLineNo(node), temp );
return( 0 );
	}
	mask |= (1<<(val-1));
	if ( *end=='\0' )
    break;
	else if ( *end==',' || *end==' ' )
	    ++end;
	else {
	    setError("In figure %s in file %s:%ld, bad part list: \"%s\".", ret->name, filename, xmlGetLineNo(node), temp );
return( 0 );
	}
    }
    if ( mask==0 )
	setError("In figure %s in file %s:%ld, empty part list.", ret->name, filename, xmlGetLineNo(node) );

return( mask );
}

struct parser {
    const char *filename;
    long lineno;
    char *next;
    int tokVal;
    enum token {
      to_and, to_or, to_not, to_lparen, to_rparen, to_comma, to_number, to_eos,
      to_topset, to_bottomset, to_part, to_majorpos, to_last, to_first,
      to_proper, to_improper
    } nextToken;
    int error;
    int just_part;
    int part_max;
    int allow_negative;
};

static void parserNextToken(struct parser *p) {
    char *pt = p->next;
    int val = 0;
    
    while ( isspace(*pt)) ++pt;
    
    if ( *pt=='\0' )
	p->nextToken = to_eos;
    else if ( *pt=='&' ) {
	p->nextToken = to_and;
	if ( pt[1]=='&' ) ++pt;
	++pt;
    } else if ( *pt=='|' ) {
	p->nextToken = to_or;
	if ( pt[1]=='|' ) ++pt;
	++pt;
    } else if ( *pt=='!' ) {
	p->nextToken = to_not;
	++pt;
    } else if ( *pt=='(' ) {
	p->nextToken = to_lparen;
	++pt;
    } else if ( *pt==')' ) {
	p->nextToken = to_rparen;
	++pt;
    } else if ( *pt==',' ) {
	p->nextToken = to_comma;
	++pt;
    } else if ( isdigit( *pt ) || ( *pt=='-' && isdigit(pt[1]) && p->allow_negative)) {
	int neg = 0;
	if ( *pt=='-' ) {
	    neg=1;
	    ++pt;
	}
	while ( isdigit( *pt ))
	    val = 10*val + *pt++-'0';
	if ( neg )
	    val = -val;
	p->nextToken = to_number;
	p->tokVal = val;
    } else if ( isalpha(*pt)) {
	char *start = pt;
	while ( isalpha(*pt) || *pt=='_' )
	    ++pt;
	if ( !p->just_part && strncmp(start,"topset",pt-start)==0 )
	    p->nextToken = to_topset;
	else if ( !p->just_part && strncmp(start,"bottomset",pt-start)==0 )
	    p->nextToken = to_bottomset;
	else if ( !p->just_part && strncmp(start,"proper",pt-start)==0 )
	    p->nextToken = to_proper;
	else if ( !p->just_part && strncmp(start,"improper",pt-start)==0 )
	    p->nextToken = to_improper;
	else if ( /*!p->just_part &&*/ strncmp(start,"last",pt-start)==0 )
	    p->nextToken = to_last;
	else if ( /*!p->just_part &&*/ strncmp(start,"first",pt-start)==0 )
	    p->nextToken = to_first;
	else if ( strncmp(start,"part",pt-start)==0 )
	    p->nextToken = to_part;
	else if ( strncmp(start,"majorpos",pt-start)==0 )
	    p->nextToken = to_majorpos;
	else if ( strncmp(start,"and",pt-start)==0 )
	    p->nextToken = to_and;
	else if ( strncmp(start,"or",pt-start)==0 )
	    p->nextToken = to_or;
	else if ( strncmp(start,"and",pt-start)==0 )
	    p->nextToken = to_not;
	else {
	    setError("In file %s:%ld unknown keyword in condition: %.*s", p->filename, p->lineno, pt-start, start );
	    fprintf(stderr,"In file %s:%ld unknown keyword in condition: %.*s\n", p->filename, p->lineno, (int) (pt-start), start );
	    p->error = 1;
	    pt = "";
	    p->nextToken = to_eos;
	}
    } else {
	setError("In file %s:%ld unexpected character in condition: '%c'", p->filename, p->lineno, *pt );
	fprintf(stderr,"In file %s:%ld unexpected character in condition: '%c'\n", p->filename, p->lineno, *pt );
	p->error = 1;
	pt = "";
	p->nextToken = to_eos;
    }
    p->next = pt;
}

static Condition *parseOr(struct parser *p);

static Condition *parsePrimary(struct parser *p) {
    Condition *op1 = NULL, *expr;
    int val1=1;
    int val2 = 0;
    
    if ( p->nextToken==to_lparen ) {
	parserNextToken(p);
	op1 = parseOr(p);
	if ( p->nextToken!=to_rparen ) {
	    fprintf(stderr,"In file %s:%ld expected right paren\n", p->filename, p->lineno );
	    p->error = 1;
	} else
	    parserNextToken(p);
    } else if ( p->nextToken==to_not ) {
        parserNextToken(p);
	op1 = parsePrimary(p);
	if ( op1==NULL )
return( NULL );
	expr = calloc(1, sizeof(Condition));
	expr->operator = op_not;
	expr->op1 = op1;
	op1 = expr;
    } else if ( p->nextToken>=to_topset ) {
	enum oper op = p->nextToken == to_topset ? op_topset :
			p->nextToken == to_bottomset ? op_bottomset :
			p->nextToken == to_majorpos ? op_majorpos :
			p->nextToken == to_proper ? op_proper :
			p->nextToken == to_improper ? op_improper :
			p->nextToken == to_last ? op_last :
			p->nextToken == to_first ? op_first :
			    op_part ;
	parserNextToken(p);
	if ( p->nextToken!=to_lparen ) {
	    if ( op==op_part || op==op_majorpos ) {
		fprintf(stderr,"In file %s:%ld expected left paren after part\n", p->filename, p->lineno );
		p->error = 1;
return( NULL );
	    }
	    val1 = 1;
	    val2 = 0;
	} else {
	    p->allow_negative = op==op_majorpos;
	    parserNextToken(p);
	    p->allow_negative = false;
	    if ( p->nextToken!=to_number ) {
		fprintf(stderr,"In file %s:%ld expected number after keyword\n", p->filename, p->lineno );
		p->error = 1;
return( NULL );
	    }
	    val1 = p->tokVal;
	    val2 = 0;
	    parserNextToken(p);
	    if ( op!=op_part && p->nextToken==to_comma ) {
		parserNextToken(p);
		if ( p->nextToken!=to_number ) {
		    fprintf(stderr,"In file %s:%ld expected number after keyword\n", p->filename, p->lineno );
		    p->error = 1;
return( NULL );
		}
		val2 = p->tokVal;
		parserNextToken(p);
	    }
	    if ( p->nextToken!=to_rparen ) {
		fprintf(stderr,"In file %s:%ld expected right paren after number\n", p->filename, p->lineno );
		p->error = 1;
return( NULL );
	    }
	    parserNextToken(p);
	}
	if ( op==op_part && (val1>p->part_max || val1<1 )) {
	    fprintf(stderr,"In file %s:%ld the part is out of bounds, must be between 1 and %d\n", p->filename, p->lineno, p->part_max );
	    p->error = 1;
return( NULL );
	}
	op1 = calloc(1,sizeof(Condition));
	op1->operator = op;
	op1->argument1 = val1;
	op1->argument2 = val2;
    }
return( op1 );
}

static Condition *parseAnd(struct parser *p) {
    Condition *op1, *op2, *expr;
    
    op1 = parsePrimary(p);
    if ( op1==NULL )
return( NULL );
    if ( p->nextToken == to_and ) {
	parserNextToken(p);
	op2 = parseAnd(p);
	if ( op2==NULL ) {
	    conditionFree(op1);
return( NULL );
	}
	expr = calloc(1,sizeof(Condition));
	expr->operator = op_and;
	expr->op1 = op1;
	expr->op2 = op2;
	op1 = expr;
    }
return( op1 );
}

static Condition *parseOr(struct parser *p) {
    Condition *op1, *op2, *expr;
    
    op1 = parseAnd(p);
    if ( op1==NULL )
return( NULL );
    if ( p->nextToken == to_or ) {
	parserNextToken(p);
	op2 = parseOr(p);
	if ( op2==NULL ) {
	    conditionFree(op1);
return( NULL );
	}
	expr = calloc(1,sizeof(Condition));
	expr->operator = op_or;
	expr->op1 = op1;
	expr->op2 = op2;
	op1 = expr;
    }
return( op1 );
}

static Condition *danceParseCondition(const char *filename,char *temp,xmlNodePtr child, int just_part, int part_max) {
    struct parser p;
    Condition *expr;
    
    memset(&p,0,sizeof(p));
    p.next = temp;
    p.filename = filename;
    p.lineno = xmlGetLineNo(child);
    p.just_part = just_part;
    p.part_max = part_max;
    parserNextToken(&p);
    expr = parseOr(&p);
    if ( p.nextToken!=to_eos ) {
	p.error = 1;
	fprintf( stderr, "In file %s:%ld garbage found after end of expression.\n", filename, p.lineno );
    }
    if ( p.error ) {
	conditionFree(expr);
	expr = NULL;
    }
return( expr );
}

void initFigureSegment(FigureSegment *fs) {
    fs->orient = mo_forward;
    fs->part_mask = -1;
    fs->drop_right_millibars = HAND_MILLIBARS_UNSET;
    fs->drop_left_millibars = HAND_MILLIBARS_UNSET;
    fs->join_right_millibars = HAND_MILLIBARS_UNSET;
    fs->join_left_millibars = HAND_MILLIBARS_UNSET;
    fs->join_left_to_point = -1;
    fs->cmd = c_end;
}

static int blockIsFirstCmd(Block *b, FigureSegment *cs) {

    if ( b==NULL )	/* If b==NULL then that means something like checking a nested if before we've filled in its parent. Which happens. And any problem should be noticed later when we check the parent */
return( true );

    if ( b->moves==cs )
return( true );
    else if ( b->moves[0].cmd==c_if && blockIsFirstCmd(b->moves[0].ifblock,cs))
return( true );

return( false );
}

static int blockIsLastCmd(Block *b, FigureSegment *cs) {

    if ( b==NULL )	/* If b==NULL then that means something like checking a nested if before we've filled in its parent. Which happens. And any problem should be noticed later when we check the parent */
return( true );

    if ( b->moves+b->move_cnt-1==cs )
return( true );
    else if ( b->moves[b->move_cnt-1].cmd==c_if )
return( blockIsLastCmd(b->moves[b->move_cnt-1].ifblock,cs ));

return( false );
}

static int personIsFirstCmd(Person *ret, FigureSegment *cs) {

    if ( &ret->figures.moves[1]==cs )
return( true );
    else if ( ret->figures.moves[1].cmd==c_if && blockIsFirstCmd(ret->figures.moves[1].ifblock,cs))
return( true );

return( false );
}

static int personIsLastCmd(Person *ret, FigureSegment *cs) {
    FigureSegment *ecs;

    if ( cs-ret->figures.moves==ret->figures.move_cnt-1 )
return( true );
    if ( cs[1].cmd==c_end || cs[1].cmd==c_resetDance )
return( true );

    ecs = ret->figures.moves + ret->figures.move_cnt-1;
    while ( ecs >= ret->figures.moves && (ecs->cmd==c_end || ecs->cmd==c_resetDance ))
	--ecs;
    if ( ecs<ret->figures.moves )
return( false );
    if ( ecs->cmd==c_if )
return( blockIsLastCmd(ecs->ifblock,cs));

return( false );
}

static int danceParseBlock(const char *filename, xmlNodePtr blockNode,Block *b, Person *ret, Dance *dance, int insideTransform ) {
    xmlNodePtr child;
    xmlChar *temp;
    int mcnt,i;
    char *end;
    struct transformation trans;
    int degrees_found=0;

    b->lineno = xmlGetLineNo(blockNode);

    mcnt = 0;
    for ( child=blockNode->children; child!=NULL; child=child->next ) {
	for ( i=0; commandNames[i]!=NULL; ++i ) {
	    if ( xmlStrcmp(child->name,(xmlChar *) commandNames[i] )==0 )
	break;
	}
	if ( commandNames[i]!=NULL )
	    ++mcnt;
	else if ( child->type!= XML_TEXT_NODE && child->type !=  XML_COMMENT_NODE )
	    fprintf( stderr, "In file %s:%ld, unexpected movement in a block or person (%s)\n", filename, xmlGetLineNo(child), (char *) child->name );
    }

    if ( mcnt!=0 ) {
	b->moves = calloc( mcnt+1, sizeof(FigureSegment));
	b->move_max = mcnt+1;
	i = 0;
	for ( child=blockNode->children; child!=NULL; child=child->next ) {
	    FigureSegment *cs = &b->moves[i];
	    int cmd = danceMatchEnum(commandNames,(char *) child->name );
	    
	    if ( cmd==-1 )
	continue;
	    danceParseTransformation(filename, child,&trans);

	    if ( i>=b->move_max-1 ) {
		b->moves = realloc(b->moves,(b->move_max += mcnt)*sizeof(FigureSegment));
		memset(b->moves+i,0,mcnt*sizeof(FigureSegment));
		cs = &b->moves[i];
	    }
	    initFigureSegment(cs);
#ifdef DEBUG
	    cs->lineno = child->line;
	    cs->copy_lineno = 0;
#endif
	    cs->cmd = cmd;
	    if ( cmd==c_copy ) {
#if 0
		if ( b!=&ret->figures ) {
		    setError("In file %s:%ld, a copy command may only occur in the top level block of a role. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
return( false );
		}
#endif
		b->move_cnt = i;
		if (( mcnt!=1 || b!=&ret->figures) && !xmlHasProp(child,(xmlChar *) "end-bar") ) {
		    setError("In file %s:%ld, a copy command without an end-bar must be the only command. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
return( false );
		} else if ( !danceParseRoleCopy(filename,child,ret,b,dance))
return( false );

		if ( !xmlHasProp(child,(xmlChar *) "end-bar"))
return( true );
		else {
		    i = b->move_cnt;
	continue;
		}
	    }

	    if ( cmd==c_if || cmd==c_elseif || cmd==c_else || cmd==c_transform ) {
		Condition *expr = NULL;
		Block *sub;
		temp = xmlGetProp(child,(xmlChar *) "condition");
		if ( temp!=NULL && (cmd==c_if || cmd==c_elseif)) {
		    expr = danceParseCondition(filename,(char *) temp,child,false,dance->part_cnt);
		    xmlFree(temp);
		    if ( expr==NULL )
return( false );
		} else if ( temp!=NULL ) {
		    setError("In file %s:%ld, an else/transform command may not have a condition. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
		    xmlFree(temp);
return( false );
		} else if ( cmd==c_if || cmd==c_elseif) {
		    setError("In file %s:%ld, an if/elseif command must have have a condition. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
return( false );
		}
		sub = calloc(1,sizeof(Block));
		if ( !danceParseBlock(filename, child,sub, ret, dance, cmd==c_transform))
return( false );
		sub->condition = expr;
		b->move_cnt = i;
		if ( cmd==c_if ) {
		    cs->ifblock = sub;
		    ++i;
	continue;
		} else if ( cmd==c_else || cmd==c_elseif ) {
		    Block *bb;
		    if ( i==0 || cs[-1].cmd != c_if ) {
			setError("In file %s:%ld, an else/elseif command must follow an if directly. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
return( false );
		    }
		    for ( bb=cs[-1].ifblock; bb->next!=NULL; bb=bb->next );
		    bb->next = sub;
	continue;
		} else /* if c_transform */ {
		    temp = xmlGetProp(child,(xmlChar *) "bar");
		    if ( temp!=NULL ) {
			fprintf( stderr, ":%ld 'bar' in a transform. You probably meant 'bar-scale'.\n", b->lineno );
		    }
		    danceBlockTransform(sub,&trans);
		    if ( sub->move_cnt==0 )
			/* Do Nothing */;
		    else if ( sub->move_cnt==1 ) {
			*cs = sub->moves[0];
			++i;
		    } else {
			b->move_max += sub->move_cnt;
			b->moves = realloc(b->moves,b->move_max*sizeof(FigureSegment));
			memcpy(b->moves+i,sub->moves,sub->move_cnt*sizeof(FigureSegment));
			memset(b->moves+i+sub->move_cnt,0,(b->move_max-i-sub->move_cnt)*sizeof(FigureSegment));
			i += sub->move_cnt;
		    }
		    free(sub->moves);
		    free(sub);
	continue;
		}
	    }

	    ++i;
	    temp = xmlGetProp(child,(xmlChar *) "bar");
	    if ( temp==NULL )
		temp = xmlGetProp(child,(xmlChar *) "beat");
	    if ( temp!=NULL ) {
		cs->millibars = round( strtod((char *) temp,NULL)*MILLIBARS_MUL);
		if ( cs->millibars<0 )
		    fprintf( stderr, "%s:%ld Negative bar count %d, \"%s\".\n", filename, xmlGetLineNo(child), cs->millibars, temp );
/* OK, what I'm trying to catch here is when I'm scaling time to distance */
/*  travelled. The bars should be 1/100th of the distance (to prevent overflow) */
/*  but sometimes I forget the decimal point. That's what I want to catch */
/* On the other hand, there are a few dances (Mill-Field.dance) where I do */
/* have a pause which should last 192 bars. So I'm only looking for big */
/* numbers within a bar-scale transform */
		if ( cs->millibars>=64*MILLIBARS_MUL && (insideTransform || cmd!=c_pause ))
		    fprintf( stderr, "%s:%ld Bar count probably too big: %d, \"%s\".\n", filename, xmlGetLineNo(child), cs->millibars, temp );
		xmlFree(temp);
	    } else if ( ( temp = xmlGetProp(child,(xmlChar *) "count"))!=NULL ) {
		cs->millibars = round( strtod((char *) temp,NULL)*MILLIBARS_MUL/dance->counts_per_bar);
		if ( cs->millibars<0 )
		    fprintf( stderr, "Negative bar count %d, \"%s\".\n", cs->millibars, temp );
		xmlFree(temp);
	    } else if ( cs->cmd==c_lineto || cs->cmd==c_curveto || cs->cmd==c_pause ) {
		cs->millibars = 1*MILLIBARS_MUL;
		fprintf( stderr, "In file %s:%ld, no bar for figure. sex=%d couple=%d role=%s\n", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
	    }
	    temp = xmlGetProp(child,(xmlChar *) "part");
	    if ( temp!=NULL ) {
		cs->part_mask = parsePartMask(filename,dance,(char *) temp, child);
		xmlFree(temp);
		if ( cs->part_mask == 0 ) {
return( false );
		}
	    }
	    temp = xmlGetProp(child,(xmlChar *) "condition");
	    if ( temp!=NULL ) {
		cs->condition = danceMatchEnum(conditionNames,(char *) temp);
		if ( (int) cs->condition==-1 ) {
		    setError("In file %s:%ld, unknown keyword for condition: %s.", filename, xmlGetLineNo(child), temp );
		    xmlFree(temp);
return( false );
		}
		xmlFree(temp);
	    }
	    degrees_found=0;
	    temp = xmlGetProp(child,(xmlChar *) "orientation");
	    if ( temp!=NULL ) {
		char buffer[200], *pt, *tpt;
		tpt = (char *) temp;
		if ( (pt=strchr((char *) temp,':'))!=NULL ) {
		    if ( pt-(char *) temp<sizeof(buffer)-2 ) {
			strncpy(buffer,(char *) temp,pt-(char *) temp);
			buffer[(int) (pt-(char *) temp)] = '\0';
			tpt = buffer;
			++pt;
			if ( strcasecmp(pt,"end")==0 )
			    cs->degrees = DEGREES_END;
			else
			    cs->degrees = strtol(pt,NULL,10);
			degrees_found=true;
		    }
		}
		cs->orient = danceMatchEnum(orientationNames, tpt);
		if ( (int) cs->orient==-1 ) {
		    setError("In file %s:%ld, unknown keyword for orientation: %s.", filename, xmlGetLineNo(child), temp );
		    xmlFree(temp);
return( false );
		}
		if ( cs->orient==mo_faceDancer && degrees_found ) {
		    cs->faceDancer = cs->degrees;
		}
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "degrees");
	    if ( temp!=NULL ) {
		if (( cs->orient==mo_rotateto || cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto )
			&& strcasecmp((char *) temp,"end")==0 )
		    cs->degrees = DEGREES_END;
		else
		    cs->degrees = strtod((char *) temp,NULL);
		xmlFree(temp);
		if ( degrees_found )
		    fprintf( stderr, "In file %s:%ld, degrees specified in two ways.\n", filename, xmlGetLineNo(child) );
	    } else if ( cs->orient==mo_rotating || cs->orient==mo_rotateto ||
		    cs->orient==mo_cw_rotateto || cs->orient==mo_ccw_rotateto ) {
		if ( !degrees_found ) {
		    setError("In file %s:%ld, rotating orientation with no degrees. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
		    xmlFree(temp);
return( false );
		}
	    }
	    temp = xmlGetProp(child,(xmlChar *) "dancer");
	    if ( temp!=NULL ) {
		cs->faceDancer = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else if ( cs->orient==mo_faceDancer && !degrees_found ) {
		setError("In file %s:%ld, face orientation with no dancer specified. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
		xmlFree(temp);
return( false );
	    }
	    /* Moveto gets facing rather than degrees, but they are represented internally the same way */
	    temp = xmlGetProp(child,(xmlChar *) "facing");
	    if ( temp!=NULL ) {
		cs->degrees = strtod((char *) temp,NULL);
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "pos");
	    if ( temp!=NULL ) {
		cs->endPoint.x = strtod((char *) temp,&end);
		while ( *end=='+' )
		    cs->endPoint.x += strtod(end+1,&end);
		if ( *end==',' ) ++end;
		else fprintf( stderr,"In file %s:%ld, Missing comma in 'pos'\n", filename, xmlGetLineNo(child) );
		cs->endPoint.y = strtod(end,&end);
		while ( *end=='+' )
		    cs->endPoint.y += strtod(end+1,&end);
		xmlFree(temp);
	    } else if ( cs->cmd==c_curveto || cs->cmd==c_lineto || cs->cmd==c_moveto ) {
		setError("In file %s:%ld, command without a pos point. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
		xmlFree(temp);
return( false );
	    }
	    temp = xmlGetProp(child,(xmlChar *) "cp1");
	    if ( temp!=NULL ) {
		cs->cp1.x = strtod((char *) temp,&end);
		while ( *end=='+' )
		    cs->cp1.x += strtod(end+1,&end);
		if ( *end==',' ) ++end;
		else fprintf( stderr,"In file %s:%ld, Missing comma in 'cp1'\n", filename, xmlGetLineNo(child) );
		cs->cp1.y = strtod(end,&end);
		while ( *end=='+' )
		    cs->cp1.y += strtod(end+1,&end);
		xmlFree(temp);
	    } else if ( cs->cmd==c_curveto ) {
		setError("In file %s:%ld, curveto without a cp1 control point. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
		xmlFree(temp);
return( false );
	    }
	    temp = xmlGetProp(child,(xmlChar *) "cp2");
	    if ( temp!=NULL ) {
		cs->cp2.x = strtod((char *) temp,&end);
		while ( *end=='+' )
		    cs->cp2.x += strtod(end+1,&end);
		if ( *end==',' ) ++end;
		else fprintf( stderr,"In file %s:%ld, Missing comma in 'cp2'\n", filename, xmlGetLineNo(child) );
		cs->cp2.y = strtod(end,&end);
		while ( *end=='+' )
		    cs->cp2.y += strtod(end+1,&end);
		xmlFree(temp);
	    } else if ( cs->cmd==c_curveto ) {
		setError("In file %s:%ld, curveto without a cp2 control point. sex=%d couple=%d role=%s", filename, xmlGetLineNo(child), ret->sex, ret->couple_number, roleNames[ret->effective_role] );
		xmlFree(temp);
return( false );
	    }
	    temp = xmlGetProp(child,(xmlChar *) "congruent");
	    if ( temp!=NULL ) {
		cs->congruent = strtol((char *) temp,NULL,16);
		xmlFree(temp);
	    }
	    if ( xmlHasProp(child,(xmlChar *) "absolute-position")!=NULL )
		cs->position_absolute = 1;

/* This is legacy notation */
	    if ( xmlHasProp(child,(xmlChar *) "drop-before-right")!=NULL )
		cs->drop_right_millibars = 0;
	    if ( xmlHasProp(child,(xmlChar *) "drop-before-left")!=NULL )
		cs->drop_left_millibars = 0;
	    temp = xmlGetProp(child,(xmlChar *) "join-after-right-right");
	    if ( temp!=NULL ) {
		cs->join_right_hand = parseOClasp(filename,(char *) temp, cs, child)| HAND_RIGHT_MASK;
		xmlFree(temp);
		cs->join_right_millibars = cs->millibars;
	    }
	    temp = xmlGetProp(child,(xmlChar *) "join-after-right-left");
	    if ( temp!=NULL ) {
		cs->join_right_hand = parseOClasp(filename,(char *) temp, cs, child)| HAND_LEFT_MASK;
		xmlFree(temp);
		cs->join_right_millibars = cs->millibars;
	    }
	    temp = xmlGetProp(child,(xmlChar *) "join-after-left-right");
	    if ( temp!=NULL ) {
		cs->join_left_hand = parseOClasp(filename,(char *) temp, cs, child)| HAND_RIGHT_MASK;
		xmlFree(temp);
		cs->join_left_millibars = cs->millibars;
	    }
	    temp = xmlGetProp(child,(xmlChar *) "join-after-left-left");
	    if ( temp!=NULL ) {
		cs->join_left_hand = parseOClasp(filename,(char *) temp, cs, child)| HAND_LEFT_MASK;
		xmlFree(temp);
		cs->join_left_millibars = cs->millibars;
	    }
	    /* The following are the new way of doing hand clasps */
	    temp = xmlGetProp(child,(xmlChar *) "drop-right-bars");
	    if ( temp==NULL )
		temp = xmlGetProp(child,(xmlChar *) "drop-right-bar");
	    if ( temp!=NULL ) {
		if ( strcasecmp((char *) temp,"end")==0 || strcasecmp((char *) temp,"after")==0 )
		    cs->drop_right_millibars = cs->millibars;
		else
		    cs->drop_right_millibars = round( strtod((char *) temp,NULL)*MILLIBARS_MUL);
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "drop-left-bars");
	    if ( temp==NULL )
		temp = xmlGetProp(child,(xmlChar *) "drop-left-bar");
	    if ( temp!=NULL ) {
		if ( strcasecmp((char *) temp,"end")==0 || strcasecmp((char *) temp,"after")==0 )
		    cs->drop_left_millibars = cs->millibars;
		else
		    cs->drop_left_millibars = round( strtod((char *) temp,NULL)*MILLIBARS_MUL);
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "join-right");
	    if ( temp!=NULL ) {
		cs->join_right_hand = parseClasp(filename,(char *) temp, cs, &cs->join_right_millibars, child);
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "join-left");
	    if ( temp!=NULL ) {
		cs->join_left_hand = parseClasp(filename,(char *) temp, cs, &cs->join_left_millibars, child);
		xmlFree(temp);
	    }
	    if ( cs->join_left_hand==(unsigned int) -1 ||
		    cs->join_right_hand==(unsigned int) -1 )
return( false );

	    danceMoveTransform(cs,&trans);

	    temp = xmlGetProp(child,(xmlChar *) "levitation");
	    cs->levitation = parseValueChange(filename,(char *) temp,cs->millibars,child);
	    if ( temp!=NULL ) xmlFree(temp);

	    temp = xmlGetProp(child,(xmlChar *) "outness");
	    cs->outness = parseValueChange(filename,(char *) temp,cs->millibars,child);
	    if ( temp!=NULL ) xmlFree(temp);


	    temp = xmlGetProp(child,(xmlChar *) "right-hand");
	    cs->rightHand = parseRThetaChange(filename,(char *) temp,cs->millibars,child);
	    if ( temp!=NULL ) xmlFree(temp);

	    temp = xmlGetProp(child,(xmlChar *) "left-hand");
	    cs->leftHand = parseRThetaChange(filename,(char *) temp,cs->millibars,child);
	    if ( temp!=NULL ) xmlFree(temp);

	    temp = xmlGetProp(child,(xmlChar *) "swordpoint");
	    cs->swordPoint = parseRThetaChange(filename,(char *) temp,cs->millibars,child);
	    if ( temp!=NULL ) xmlFree(temp);

	    temp = xmlGetProp(child,(xmlChar *) "join-left-to-point");
	    if ( temp!=NULL ) {
		cs->join_left_to_point = strtol((char *) temp,&end,10);
		if ( *end!='|' )
		    cs->join_left_millibars = cs->millibars;
		else if ( strcasecmp(end,"|end")==0 )
		    cs->join_left_millibars = cs->millibars;
		else
		    cs->join_left_millibars = round( MILLIBARS_MUL * strtod(end+1,NULL));
		xmlFree(temp);
	    }


	    temp = xmlGetProp(child,(xmlChar *) "start-honour");
	    if ( temp!=NULL ) {
		cs->start_honour = MILLIBARS_MUL*strtod((char *) temp,NULL);
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "end-honour");
	    if ( temp!=NULL ) {
		if ( strcasecmp((char *)temp,"end")==0 )
		    cs->end_honour = cs->millibars;
		else
		    cs->end_honour = MILLIBARS_MUL*strtod((char *) temp,NULL);
		xmlFree(temp);
	    }

	}
	b->move_cnt = i;
    }
    for ( i=0; i<b->move_cnt; ++i ) {
	FigureSegment *cs = &b->moves[i];
	if ( cs->cmd==c_if ) {
	    /* Every if must end with an else */
	    /* Only one else clause allowed per if */
	    /* all branches of the if take the same amount of time */
	    int cnt;
	    Block *subb;
	    for ( subb=cs->ifblock; subb!=NULL; subb=subb->next ) {
	    	if ( subb->condition!=NULL && subb->next!=NULL )
		    /* That's good. an if/elseif not at the end */;
		else if ( subb->condition==NULL && subb->next==NULL )
		    /* Also good. an else at the end */;
		else if ( subb->condition==NULL ) {
		    setError("In file %s:%ld, An 'else' statement may only appear at the end of an 'if'. sex=%d couple=%d role=%s", filename, subb->lineno, ret->sex, ret->couple_number, roleNames[ret->effective_role] );
return( false );
		} else if ( subb->next==NULL ) {
		    setError("In file %s:%ld, An 'if' statement must end with an 'else' not an 'if' or 'elseif'. sex=%d couple=%d role=%s", filename, subb->lineno, ret->sex, ret->couple_number, roleNames[ret->effective_role] );
return( false );
		}
	    }
	    cnt = -1;
	    for ( subb=cs->ifblock; subb!=NULL; subb=subb->next ) {
		if ( cnt==-1 )
		    cnt = blockCountMillibars(subb);
		else if ( cnt!=blockCountMillibars(subb) )
	    break;
	    }
	    if ( subb!=NULL ) {
		if (( cs->ifblock->condition->operator == op_first && personIsFirstCmd(ret,cs) ) ||
			( cs->ifblock->condition->operator == op_last && personIsLastCmd(ret,cs))) {
		    /* That's OK. The intro or finale can be of a different length */;
		} else if ( strcasecmp(dance->name,"The Woodcock")!=0 &&
			strcasecmp(dance->name,"Renewed Friendship")!=0 &&
			strcasecmp(dance->name,"Ribbon Dance")!=0 ) {
		    /* In the woodcock there are ifs so that at the end of the last figure of the last set we do a finale */
		    fprintf( stderr, "In file %s:%ld the branches of the if have different times.\n", filename, cs->ifblock->lineno );
		    for ( subb=cs->ifblock; subb!=NULL; subb=subb->next )
			fprintf( stderr, "  line %ld: %.3f\n", subb->lineno, blockCountMillibars(subb)/MILLIBARS_MUL );
		}
	    }
	}
    }
return( true );
}

static enum sex parseSex(xmlChar *temp, const char *filename, xmlNodePtr node) {

    if ( temp!=NULL ) {
	if ( *temp=='m' || *temp=='M' )
return( s_male );
	else if ( *temp=='f' || *temp=='F' )
return( s_female );
	else if ( *temp=='l' || *temp=='L' )
return( s_left );
	else if ( *temp=='c' || *temp=='C' )
return( s_center );
	else if ( *temp=='r' || *temp=='R' )
return( s_right );
	else {
	    fprintf(stderr,"In file %s:%ld, bad sex %s.", filename, xmlGetLineNo(node), temp );
return( s_unknown );
	}
    }
return( s_unknown );
}

static Person *danceParsePerson(const char *filename,xmlDocPtr doc,xmlNodePtr personNode, Person *ret, Dance *dance) {
    xmlChar *temp;

    ret->now_left_hand_point = -1;

    temp = xmlGetProp(personNode,(xmlChar *) "couple");
    if ( temp!=NULL ) {
	ret->couple_number = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }
    temp = xmlGetProp(personNode,(xmlChar *) "sex");
    if ( temp!=NULL ) {
	ret->sex = parseSex(temp,filename,personNode);
	if ( ret->sex == s_unknown )
	    ret->sex = s_male;
	xmlFree(temp);
	ret->effective_sex = ret->sex;
    }
    temp = xmlGetProp(personNode,(xmlChar *) "role");
    if ( temp!=NULL ) {
	ret->effective_role = danceMatchEnum(roleNames,(char *) temp);
	if ( (int) (ret->effective_role)==-1 ) {
	    setError("In file %s:%ld, bad role %s.", filename, xmlGetLineNo(personNode), temp );
	    xmlFree(temp);
return( NULL );
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(personNode,(xmlChar *) "display-role");
    if ( temp!=NULL ) {
	ret->display_role = danceMatchEnum(roleNames,(char *) temp);
	if ( (int) (ret->display_role)==-1 ) {
	    setError("In file %s:%ld, bad role %s.", filename, xmlGetLineNo(personNode), temp );
	    xmlFree(temp);
return( NULL );
	}
	xmlFree(temp);
    } else
	ret->display_role = r_last_unused;
    if ( !danceParseBlock(filename, personNode,&ret->figures, ret, dance,false))
return( NULL );

    if ( ret->figures.move_cnt>0 && ret->figures.moves[ret->figures.move_cnt-1].cmd!=c_end ) {
	initFigureSegment(& ret->figures.moves[ret->figures.move_cnt  ]);
    }

    if ( ret->figures.move_cnt>0 && ret->figures.moves[0].cmd == c_moveto )
	ret->actualFigureStartPos = ret->figures.moves[0].endPoint;
		    
return( ret );
}

static Dance *danceFindFigure(Figure *figure, char *name) {
    int i;

    for ( i=0; i<figure->comp_cnt; ++i ) {
	if ( figure->components[i]!=NULL &&
		figure->components[i]->name!=NULL &&
		strcasecmp(figure->components[i]->name,name)==0 )
return( figure->components[i] );
    }
return( NULL );
}

static char *xmlNodeListGetEverything(xmlDocPtr doc,xmlNodePtr thing) {
    /* I want to produce a string that when parsed by libxml will yield the */
    /*  same data structure. The point is that I sometimes include html markup*/
    /*  I don't want to look at that as a tree, to me it's an opaque thing */
    /*  that I just want to pass on as is */
    xmlBufferPtr buf;
    int len;
    char *ret;

    buf = xmlBufferCreate();
    len = xmlNodeDump(buf,doc,thing,0,0);
    if ( len!=-1 ) {
	ret = malloc(len+1);
	memcpy(ret,buf->content,len);
	ret[len]='\0';
    } else
	ret = NULL;
    xmlBufferFree(buf);
return( ret );
}

static int parseCallName(const char *filename, xmlNodePtr child,Figure *ret,int ci, int part_mask) {
    xmlChar *temp;

    ret->calls[ci].callName = (char *) xmlGetProp(child,(xmlChar *) "name");
    ret->calls[ci].audioFilename = (char *) xmlGetProp(child,(xmlChar *) "audio");
    ret->calls[ci].longaudioFilename = (char *) xmlGetProp(child,(xmlChar *) "long-audio");
    if ( ret->calls[ci].longaudioFilename==NULL )
    ret->calls[ci].longaudioFilename = (char *) xmlGetProp(child,(xmlChar *) "longaudio");
    ret->calls[ci].part_mask = (unsigned int) part_mask;
    temp = xmlGetProp(child,(xmlChar *) "bar");
    if ( temp==NULL )
	temp = xmlGetProp(child,(xmlChar *) "beat");
    if ( temp!=NULL ) {
	ret->calls[ci].millibars = MILLIBARS_MUL*strtod((char *) temp,NULL);
	xmlFree(temp);
    } else if ( ( temp = xmlGetProp(child,(xmlChar *) "count"))!=NULL ) {
	ret->calls[ci].millibars = MILLIBARS_MUL*strtod((char *) temp,NULL)/2;
	xmlFree(temp);
    } else {
	ret->calls[ci].millibars = 1*MILLIBARS_MUL;
	fprintf(stderr,"In figure %s in file %s:%ld, no bar for call-name.\n", ret->name, filename, xmlGetLineNo(child) );
    }
    temp = xmlGetProp(child,(xmlChar *) "offset-sec");
    if ( temp!=NULL ) {
	ret->calls[ci].offset_millisecs = 1000*strtod((char *) temp,NULL);
	xmlFree(temp);
    } else
	ret->calls[ci].offset_millisecs = 0;
    temp = xmlGetProp(child,(xmlChar *) "actual-bar");
    if ( temp==NULL ) {
	temp = xmlGetProp(child,(xmlChar *) "actual-bars");
	if ( temp!=NULL )
	    setError("In figure %s in file %s:%ld, used 'actual-bars' instead of 'actual-bar'.", ret->name, filename, xmlGetLineNo(child) );
    }
    if ( temp!=NULL ) {
	ret->calls[ci].actual_millibars = MILLIBARS_MUL*strtod((char *) temp,NULL);
	xmlFree(temp);
    } else
	ret->calls[ci].actual_millibars = ret->calls[ci].millibars;
    temp = xmlGetProp(child,(xmlChar *) "audio-bar");
    if ( temp==NULL )
	temp = xmlGetProp(child,(xmlChar *) "audio-bars");
    if ( temp!=NULL ) {
	ret->calls[ci].audio_millibars = MILLIBARS_MUL*strtod((char *) temp,NULL);
	xmlFree(temp);
    } else
	ret->calls[ci].audio_millibars = ret->calls[ci].millibars;
    temp = xmlGetProp(child,(xmlChar *) "music");
    if ( temp!=NULL ) {
	if ( strlen((char *) temp)>sizeof(ret->calls[ci].musicName)-1 )
	    setError("In figure %s in file %s:%ld, length of the music of a call-name is too long, must be fewer than %d characters.", ret->name, filename, xmlGetLineNo(child), sizeof(ret->calls[ci].musicName) );
	else
	    strcpy(ret->calls[ci].musicName,(char *) temp);
	xmlFree(temp);
    }
    temp = xmlGetProp(child,(xmlChar *) "part");
    if ( temp!=NULL ) {
	if ( part_mask != (unsigned int) -1 ) {
	    setError("In figure %s in file %s:%ld, attempt to specify a 'part' on a call-name already inside an if.", ret->name, filename, xmlGetLineNo(child) );
	    figureFree(ret);
return( -1 );
	}
	ret->calls[ci].part_mask = parsePartMask(filename,ret,(char *) temp, child);
	xmlFree(temp);
	if ( ret->calls[ci].part_mask == 0 ) {
	    figureFree(ret);
return( -1 );
	}
    }
    temp = xmlGetProp(child,(xmlChar *) "clap");
    if ( temp==NULL ) {
	temp = xmlGetProp(child,(xmlChar *) "snap");
	if ( temp!=NULL )
	    ret->calls[ci].snap_not_clap = true;
	else {
	    temp = xmlGetProp(child,(xmlChar *) "stamp");
	    if ( temp!=NULL )
		ret->calls[ci].stamp_not_clap = true;
	}
    }
    if ( temp==NULL ) {
	temp = xmlGetProp(child,(xmlChar *) "claps");
	if ( temp!=NULL )
	    fprintf(stderr, "In figure %s in file %s:%ld, use attribute 'clap' rather than 'claps'.\n", ret->name, filename, xmlGetLineNo(child) );
    }
    if ( temp!=NULL ) {
	char *pt = (char *) temp, *end;
	int cnt=0;
	forever {
	    while ( *pt==' ' ) ++pt;
	    if ( !isdigit(*pt) && *pt!='.' )
	break;
	    strtod(pt,&end);
	    ++cnt;
	    pt = end;
	    while ( *pt==' ' ) ++pt;
	    if ( *pt!=',' )
	break;
	    ++pt;
	}
	if ( *pt!='\0' ) {
	    fprintf(stderr,"In figure %s in file %s:%ld, the clap field is not a comma separated list of numbers: '%s'.\n", ret->name, filename, xmlGetLineNo(child), temp );
	} else if ( cnt==0 ) {
	    fprintf(stderr,"In figure %s in file %s:%ld, nothing in the clap field.\n", ret->name, filename, xmlGetLineNo(child) );
	} else {
	    pt = (char *) temp;
	    ret->calls[ci].claplist_millibars = malloc((cnt+1)*sizeof(unsigned short));
	    cnt = 0;
	    forever {
		while ( *pt==' ' ) ++pt;
		if ( !isdigit(*pt) && *pt!='.' )
	    break;
		ret->calls[ci].claplist_millibars[cnt] = MILLIBARS_MUL * strtod(pt,&end);
		++cnt;
		pt = end;
		while ( *pt==' ' ) ++pt;
		if ( *pt!=',' )
	    break;
		++pt;
	    }
	    ret->calls[ci].claplist_millibars[cnt] = CLAP_DONE;
	}
    }
    temp = xmlGetProp(child,(xmlChar *) "honour-start");
    if ( temp!=NULL ) {
	ret->calls[ci].honour_start = MILLIBARS_MUL*strtod((char *) temp,NULL);
	xmlFree(temp);
	fprintf(stderr,"In figure %s in file %s:%ld, the honour-start field is deprecated add to moves instead: '%s'.\n", ret->name, filename, xmlGetLineNo(child), temp );
    }
    temp = xmlGetProp(child,(xmlChar *) "honour-end");
    if ( temp!=NULL ) {
	if ( strcasecmp((char *) temp,"end")==0 )
	    ret->calls[ci].honour_end = ret->calls[ci].millibars;
	else
	    ret->calls[ci].honour_end = MILLIBARS_MUL*strtod((char *) temp,NULL);
	xmlFree(temp);
    }
return( ci+1 );
}

static int conditionFigureMask(Condition *expr,int part_cnt) {
    int sub,val;

    switch(expr->operator) {
      case op_part:
return( 1<<(expr->argument1-1) );
      case op_not:
        sub = conditionFigureMask(expr->op1,part_cnt);
	val = 0;
	if ( sub&(LAST_MASK|NOTLAST_MASK) ) {
	    val = sub & (LAST_MASK|NOTLAST_MASK);
	    val ^= (LAST_MASK|NOTLAST_MASK);
	    sub &= ~(LAST_MASK|NOTLAST_MASK);
	}
	if ( sub&(FIRST_MASK|NOTFIRST_MASK) ) {
	    val = sub & (FIRST_MASK|NOTFIRST_MASK);
	    val ^= (FIRST_MASK|NOTFIRST_MASK);
	    sub &= ~(FIRST_MASK|NOTFIRST_MASK);
	}
	if ( val!=0 && sub==0 )
return( val );

return( (((1<<part_cnt)-1) & ~sub) | val );
      case op_or:
return( conditionFigureMask(expr->op1,part_cnt)|conditionFigureMask(expr->op2,part_cnt));
      case op_and:
return( conditionFigureMask(expr->op1,part_cnt)&conditionFigureMask(expr->op2,part_cnt));
      default:
return( 0 );

      case op_last:
return( LAST_MASK );
      case op_first:
return( FIRST_MASK );
    }
}

static int parseCallIf(const char *filename, xmlNodePtr child,Figure *ret,int ci, int *masks) {
    xmlChar *temp;
    Condition *expr = NULL;
    int cmd;
    int mask;
    xmlNodePtr grandchild;

    if (  xmlStrcmp(child->name,(xmlChar *) "if" )==0 )
	cmd = c_if;
    else if ( xmlStrcmp(child->name,(xmlChar *) "elseif" )==0 )
	cmd = c_elseif;
    else
	cmd = c_else;

    temp = xmlGetProp(child,(xmlChar *) "condition");
    if ( temp!=NULL && (cmd==c_if || cmd==c_elseif)) {
	expr = danceParseCondition(filename,(char *) temp,child,true,ret->part_cnt);
	xmlFree(temp);
	if ( expr==NULL ) {
	    figureFree(ret);
return( -1 );
	}
    } else if ( temp!=NULL ) {
	setError("In file %s:%ld, an else command may not have a condition.", filename, xmlGetLineNo(child));
	figureFree(ret);
	xmlFree(temp);
return( -1 );
    } else if ( cmd==c_if || cmd==c_elseif) {
	setError("In file %s:%ld, an if/elseif command must have have a condition.", filename, xmlGetLineNo(child) );
	figureFree(ret);
return( -1 );
    }

    if ( expr!=NULL ) {
	mask = conditionFigureMask(expr,ret->part_cnt);
	conditionFree(expr);
    } else {
	/* Bug here. Don't handle things like "last | part(3)" */
	if ( *masks&(LAST_MASK|NOTLAST_MASK|FIRST_MASK|NOTFIRST_MASK)) {
	    mask = 0;
	    if ( *masks&(LAST_MASK|NOTLAST_MASK) )
		mask = (*masks ^ (LAST_MASK|NOTLAST_MASK)) & (LAST_MASK|NOTLAST_MASK);
	    if ( *masks&(FIRST_MASK|NOTFIRST_MASK) )
		mask |= (*masks ^ (FIRST_MASK|NOTFIRST_MASK)) & (FIRST_MASK|NOTFIRST_MASK);
	} else {
	    mask = ~(-1<<ret->part_cnt);
	    mask &= ~*masks;
	    if ( mask==0 ) {
		setError("In file %s:%ld, else branch is never reached, are you specifying \"parts\" properly.", filename, xmlGetLineNo(child), (char *) temp );
		figureFree(ret);
		xmlFree(temp);
return( -1 );
	    }
	}
    }
    if ( mask==0 ) {
	temp = xmlGetProp(child,(xmlChar *) "condition");
	setError("In file %s:%ld, the condition must specify at least one part %s.", filename, xmlGetLineNo(child), (char *) temp );
	figureFree(ret);
	xmlFree(temp);
return( -1 );
    }
    *masks |= mask;
    
    for ( grandchild = child->children; grandchild!=NULL; grandchild=grandchild->next ) {
	if ( xmlStrcmp(grandchild->name,(xmlChar *) "call-name" )==0 ) {
	    ci = parseCallName(filename,grandchild,ret,ci, mask);
	    if ( ci==-1 )
return( -1 );
	} else if( grandchild->type!= XML_TEXT_NODE && grandchild->type != XML_COMMENT_NODE ) {
	    setError("In file %s:%ld, expecting 'call-name' found %s.", filename, xmlGetLineNo(grandchild), (char *) (grandchild->name) );
	    figureFree(ret);
return( -1 );
	}
    }
return( ci );
}

static Figure *parseBBs(Figure *ret,xmlNodePtr bbNode, const char *filename) {
    xmlNodePtr child;
    xmlChar *temp;
    BBList *cur, *last;

    ret->bbs = last = calloc( 1,sizeof( BBList ));
    last->automatic = true;
    temp = xmlGetProp(bbNode,(xmlChar *) "initial");
    if ( temp!=NULL ) {
	if ( strcasecmp((char *) temp,"auto")==0 )
	    last->automatic = true;
	else if ( sscanf( (char *) temp, "%d,%d,%d,%d", &last->bb.minx, &last->bb.miny, &last->bb.maxx, &last->bb.maxy )== 4 )
	    last->automatic = false;
	else {
	    setError("In figure %s in file %s:%ld, unparsable initial value '%s' must be either 'auto' or 'minx,miny,maxx,maxy'.", ret->name, filename, xmlGetLineNo(bbNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    } else {
	temp = xmlGetProp(bbNode,(xmlChar *) "minx");
	if ( temp!=NULL ) {
	    last->bb.minx = strtol((char *) temp,NULL,10);
	    xmlFree(temp);
	    temp = xmlGetProp(bbNode,(xmlChar *) "maxx");
	    if ( temp!=NULL ) {
		last->bb.maxx = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else {
		setError("In figure %s in file %s:%ld, maxx not specified when minx was.", ret->name, filename, xmlGetLineNo(bbNode) );
		xmlFree(temp);
		figureFree(ret);
return( NULL );
	    }
	    temp = xmlGetProp(bbNode,(xmlChar *) "miny");
	    if ( temp!=NULL ) {
		last->bb.miny = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else {
		setError("In figure %s in file %s:%ld, miny not specified when minx was.", ret->name, filename, xmlGetLineNo(bbNode) );
		xmlFree(temp);
		figureFree(ret);
return( NULL );
	    }
	    temp = xmlGetProp(bbNode,(xmlChar *) "maxy");
	    if ( temp!=NULL ) {
		last->bb.maxy = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else {
		setError("In figure %s in file %s:%ld, maxy not specified when minx was.", ret->name, filename, xmlGetLineNo(bbNode) );
		xmlFree(temp);
		figureFree(ret);
return( NULL );
	    }
	    last->automatic = false;
	}
    }

    for ( child = bbNode->children; child!=NULL; child=child->next ) {
	if ( child->type== XML_TEXT_NODE ||
	      child->type == XML_COMMENT_NODE )
    continue;
	cur = calloc(1,sizeof(BBList));
	last->next = cur;
	last = cur;

	temp = xmlGetProp(child,(xmlChar *) "bar");
	if ( temp==NULL )
	    temp = xmlGetProp(child,(xmlChar *) "bars");
	if ( temp!=NULL ) {
	    cur->millibars = round( MILLIBARS_MUL * strtod((char *) temp,NULL));
	    xmlFree(temp);
	}
	temp = xmlGetProp(child,(xmlChar *) "extra-frames");
	if ( temp!=NULL ) {
	    cur->extra_frames = strtol((char *) temp,NULL,10);
	    xmlFree(temp);
	}
	if ( cur->extra_frames==0 && cur->millibars==0 )
	    fprintf(stderr, "In figure %s in file %s:%ld, bars is not specified.", ret->name, filename, xmlGetLineNo(child) );

	if ( xmlStrcmp(child->name,(xmlChar *) "auto" )==0 )
	    cur->automatic = 1;
	else if ( xmlStrcmp(child->name,(xmlChar *) "fixed" )==0 )
	    cur->fixed = 1;
	else if ( xmlStrcmp(child->name,(xmlChar *) "rectto" )==0 ) {
	    temp = xmlGetProp(child,(xmlChar *) "bb");
	    if ( temp!=NULL ) {
		if ( sscanf( (char *) temp, "%d,%d,%d,%d", &cur->bb.minx, &cur->bb.miny, &cur->bb.maxx, &cur->bb.maxy )!= 4 ) {
		    setError("In figure %s in file %s:%ld, unparsable initial value '%s' must be either 'auto' or 'minx,miny,maxx,maxy'.", ret->name, filename, xmlGetLineNo(child), temp );
		    xmlFree(temp);
		    figureFree(ret);
return( NULL );
		}
		xmlFree(temp);
	    } else {
		temp = xmlGetProp(child,(xmlChar *) "minx");
		if ( temp!=NULL ) {
		    cur->bb.minx = strtol((char *) temp,NULL,10);
		    xmlFree(temp);
		    temp = xmlGetProp(child,(xmlChar *) "maxx");
		    if ( temp!=NULL ) {
			cur->bb.maxx = strtol((char *) temp,NULL,10);
			xmlFree(temp);
		    } else {
			setError("In figure %s in file %s:%ld, maxx not specified when minx was.", ret->name, filename, xmlGetLineNo(child) );
			xmlFree(temp);
			figureFree(ret);
return( NULL );
		    }
		    temp = xmlGetProp(child,(xmlChar *) "miny");
		    if ( temp!=NULL ) {
			cur->bb.miny = strtol((char *) temp,NULL,10);
			xmlFree(temp);
		    } else {
			setError("In figure %s in file %s:%ld, miny not specified when minx was.", ret->name, filename, xmlGetLineNo(child) );
			xmlFree(temp);
			figureFree(ret);
return( NULL );
		    }
		    temp = xmlGetProp(child,(xmlChar *) "maxy");
		    if ( temp!=NULL ) {
			cur->bb.maxy = strtol((char *) temp,NULL,10);
			xmlFree(temp);
		    } else {
			setError("In figure %s in file %s:%ld, maxy not specified when minx was.", ret->name, filename, xmlGetLineNo(child) );
			xmlFree(temp);
			figureFree(ret);
return( NULL );
		    }
		} else {
		    setError("In figure %s in file %s:%ld, a rectto element must either contain a bb attribute or all of minx,miny,maxx,maxy attributes.", ret->name, filename, xmlGetLineNo(child) );
		    xmlFree(temp);
		    figureFree(ret);
return( NULL );
		}
	    }
	} else {
	    setError("In figure %s in file %s:%ld, unknown element '%s' in bb.", ret->name, filename, xmlGetLineNo(child), (char *) child->name );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
    }
return( ret );
}

static void danceMakeDefaultMiddleSet(MusicDescription *md) {
    MusicSegment temp;
    int mbars, lmb;
    MusicSegment *ms;
    long len;
    int cnt;
    double frac;
    /* look at the music between the end of the first minorset and the start of the last */
    /* First... is there anything there? */
    /* Second... is its length (in millisecs) approximately an integral multiple of the length of the first minorset */
    /*   ( and an integral multiple of the last minorset) */
    /* If so the create a new segment spanning this range and figure the bar count */

    if ( md->minorset==NULL || (md->lastminorset==NULL && md->ending==NULL) )
return;
    memset(&temp,0,sizeof(temp));
    temp.autogen = true;
    temp.type = it_fit;
    ms = md->minorset;
    mbars = 0;
    while ( true ) {
	mbars += ms->duration_millibars;
	if ( ms->next==NULL )
    break;
	ms = ms->next;
    }
    temp.start = ms->end;
    temp.end = md->lastminorset!=NULL ? md->lastminorset->start : md->ending->start;
    md->minorset_millibars = mbars;

    len = ms->end - md->minorset->start;
    frac = ((double) (temp.end-temp.start))/(double) len;
    cnt = (int) round( frac );
    if ( cnt<= 0 )
return;
    frac /= cnt;
    if ( frac>1.05 && frac<.95 )
return;
    temp.duration_millibars = cnt*mbars;

    if ( md->lastminorset !=NULL ) {
	ms = md->lastminorset;
	lmb = 0;
	while ( true ) {
	    lmb += ms->duration_millibars;
	    if ( ms->next==NULL )
	break;
	    ms = ms->next;
	}
	if ( lmb!=mbars )
return;

	len = ms->end - md->lastminorset->start;
	frac = ((double) (temp.end-temp.start))/(double) len;
	cnt = (int) round( frac );
	frac -= cnt;
	if ( frac<.1 && frac>-.1 ) {
	    if ( cnt*lmb!=temp.duration_millibars )
return;
	} else
return;
    }
    md->middlesets = malloc(sizeof(MusicSegment));
    md->middleset_count = cnt;
    *md->middlesets = temp;
}
	
static MusicSegment *danceParseMusicSegments(const char *filename,MusicDescription *md,xmlNodePtr section) {
    xmlNodePtr child;
    xmlChar *temp;
    MusicSegment *sofar=NULL, *last=NULL, *cur;
    int start, end, duration;
    int fadein_millis;
    float fadein_initial_level;
    enum interpolation_type type;
    char *name;

    for ( child = section->children; child!=NULL; child=child->next ) {
	if ( child->type!=XML_ELEMENT_NODE )
    continue;
	if ( strcmp((char *)(child->name),"copy")==0 )
	    type = it_copy;
	else if ( strcmp((char *)(child->name),"fit")==0 )
	    type = it_fit;
	else if ( strcmp((char *)(child->name),"fit2set")==0 )
	    type = it_fit2set;
	else if ( strcmp((char *)(child->name),"fit2lastset")==0 )
	    type = it_fit2lastset;
	else {
	    setError("In file %s:%ld, a music segment contains an unexpected child element '%s'.", filename, xmlGetLineNo(child), (char *) child->name );
    continue;
	}

	temp = xmlGetProp(child,(xmlChar *) "start");
	if ( temp!=NULL ) {
	    start = strtol((char *) temp,NULL,10);
	    xmlFree(temp);
	} else {
	    setError("In file %s:%ld, a music segment must have a start time (in milliseconds).", filename, xmlGetLineNo(child) );
    continue;
	}
	temp = xmlGetProp(child,(xmlChar *) "end");
	if ( temp!=NULL ) {
	    end = strtol((char *) temp,NULL,10);
	    xmlFree(temp);
	} else {
	    setError("In file %s:%ld, a music segment must have an end time (in milliseconds).", filename, xmlGetLineNo(child) );
    continue;
	}
	if ( end<start ) {
	    setError("In file %s:%ld, a music segment's start time must be before it's end.", filename, xmlGetLineNo(child) );
    continue;
	}
	    
	temp = xmlGetProp(child,(xmlChar *) "duration-bar");
	duration = 0;
	if ( type==it_copy || type==it_fit2set || type==it_fit2lastset ) {
	    if ( temp!=NULL ) {
		setError("In file %s:%ld, a copy/fit2set segment must not have a duration.", filename, xmlGetLineNo(child) );
		xmlFree(temp);
	    }
	} else if ( type==it_fit ) {
	    if ( temp!=NULL ) {
		duration = round( MILLIBARS_MUL * strtod((char *) temp,NULL));
		xmlFree(temp);
	    } else {
		setError("In file %s:%ld, a fit segment must specify a duration.", filename, xmlGetLineNo(child) );
    continue;
	    }
	} else {
	    setError("In file %s:%ld, unexpected element in music segment (must be: copy or fit).", filename, xmlGetLineNo(child), temp );
    continue;
	}

	temp = xmlGetProp(child,(xmlChar *) "fade-in");
	fadein_initial_level = 0;
	if ( temp==NULL ) {
	    fadein_millis = 0;
	} else {
	    char *end;
	    fadein_millis = strtol((char *)temp,&end,10);
	    if ( *end==',' )
		fadein_initial_level = strtod((char *)end+1,&end);
	    if ( *end!='\0' )
		setError("In file %s:%ld, junk found in fade-in.", filename, xmlGetLineNo(child), end );
	}

	name = (char *) xmlGetProp(child,(xmlChar *) "name");

	cur = calloc(1,sizeof(MusicSegment));
	if ( name!=NULL ) {
	    cur->name = strdup((char *) name);
	    xmlFree(name);
	}
	cur->start = start;
	cur->end   = end;
	cur->duration_millibars = duration;
	cur->fadein_duration = fadein_millis;
	cur->fadein_initial_level = fadein_initial_level;
	cur->type  = type;
	if ( last == NULL )
	    sofar = cur;
	else
	    last->next = cur;
	last = cur;
    }
return( sofar );
}

static long parseLong( const char *filename, xmlNodePtr sbNode, const char *attrname ) {
    xmlChar *temp;
    char *end;
    long val;

    temp = xmlGetProp(sbNode,(xmlChar *) attrname);
    if ( temp==NULL ) {
	setError("In file %s:%ld, no '%s' attribute to <set-bounds> tag.", filename, xmlGetLineNo(sbNode), attrname );
return( -1 );
    }
    val = strtol((char *) temp,&end,10);
    if ( val<0 ) {
	setError("In file %s:%ld, value of '%s' attribute to <set-bounds> tag must be positive.", filename, xmlGetLineNo(sbNode), attrname );
	xmlFree(temp);
return( -1 );
    }
    if ( *end!='\0' ) {
	setError("In file %s:%ld, garbage after integer end in '%s' attribute to <set-bounds> tag (%s).", filename, xmlGetLineNo(sbNode), attrname, (char *) temp );
	xmlFree(temp);
return( -1 );
    }
    xmlFree(temp);
return( val );
}

static int musicSegmentBars(MusicSegment *ms) {
    int cnt = 0;
    while ( ms!=NULL ) {
	cnt += ms->duration_millibars;
	ms = ms->next;
    }
return( (int) (cnt/MILLIBARS_MUL) );
}

static void danceParseSetBounds(const char *filename,MusicDescription *md,xmlNodePtr sbNode) {
    int set_bars;
    long intro_start, intro_end;
    long final_end;
    long *vals;
    int i,cnt;
    char *pt, *end;
    xmlChar *temp;
    int avgmilli, mymilli;

    if ( (set_bars = parseLong( filename, sbNode, "set-bars" ))<0 )
return;
    if ( (intro_start = parseLong( filename, sbNode, "intro-start" ))<0 )
return;
    if ( (intro_end = parseLong( filename, sbNode, "intro-end" ))<0 )
return;
    if ( (final_end = parseLong( filename, sbNode, "final-end" ))<0 )
return;
    if ( md->ending!=NULL && final_end<=md->ending->start ) {
	setError("In file %s:%ld, value of 'final-end' attribute to <set-bounds> tag (%ld) equals ending.start.", filename, xmlGetLineNo(sbNode), final_end );
return;
    }
    if ( md->minorset!=NULL && musicSegmentBars(md->minorset)!=set_bars ) {
	if ( musicSegmentBars(md->minorset)%set_bars==0 )
	    fprintf( stderr, "In file %s:%ld, value of 'set-bars' attribute to <set-bounds> tag (%d) does not match that of <minorset> tag (but it does divide it evenly.\n", filename, xmlGetLineNo(sbNode), set_bars );
	else {
	    setError("In file %s:%ld, value of 'set-bars' attribute to <set-bounds> tag (%d) does not match that of <minorset> tag.", filename, xmlGetLineNo(sbNode), set_bars );
return;
	}
    }
    
    temp = xmlGetProp(sbNode,(xmlChar *) "set-ends");
    if ( temp==NULL ) {
	setError("In file %s:%ld, no 'set-ends' attribute to <set-bounds> tag.", filename, xmlGetLineNo(sbNode) );
return;
    }

    cnt = 0;
    for ( pt=(char *) temp; (pt=strchr(pt,','))!=NULL ; ) {
	++cnt;
	++pt;
    }
    vals = malloc((++cnt)*sizeof(long));
    pt = (char *) temp;
    for ( i=0; i<cnt; ++i ) {
	vals[i] = strtol(pt,&end,10);
	while ( *end==' ' ) ++end;
	if ( vals[i]<=0 ) {
	    setError("In file %s:%ld, value of 'set-ends' attribute to <set-bounds> tag must be positive (%s).", filename, xmlGetLineNo(sbNode), (char *) temp );
	    free(vals);
	    xmlFree(temp);
return;
	}
	if ( i!=0 && vals[i]<=vals[i-1] ) {
	    setError("In file %s:%ld, value of 'set-ends' attribute to <set-bounds> tag must be bigger than previous value %ld (%s).", filename, xmlGetLineNo(sbNode), vals[i], (char *) temp );
	    free(vals);
	    xmlFree(temp);
return;
	}
	if ( vals[i]<intro_end || vals[i]>final_end ) {
	    if ( vals[i]<intro_end )
		setError("In file %s:%ld, value of 'set-ends' attribute to <set-bounds> tag (%ld) must be bigger than intro_end %ld (%s).", filename, xmlGetLineNo(sbNode), vals[i], intro_end, (char *) temp );
	    else
		setError("In file %s:%ld, value of 'set-ends' attribute to <set-bounds> tag (%ld) must be less than final_end %ld (%s).", filename, xmlGetLineNo(sbNode), vals[i], final_end, (char *) temp );
	    free(vals);
	    xmlFree(temp);
return;
	}
	if ( *end=='\0' ) {
	    assert(i==cnt-1);
    break;
	} else if ( *end!=',' ) {
	    setError("In file %s:%ld, values of 'set-ends' attribute to <set-bounds> must be separated by commas (%s).", filename, xmlGetLineNo(sbNode), (char *) temp );
	    free(vals);
	    xmlFree(temp);
return;
	}
	pt = end+1;
    }

    avgmilli = (vals[cnt-1]-intro_end)/cnt;
    for ( i=0; i<cnt; ++i ) {
	if ( i==0 )
	    mymilli = vals[0]-intro_end;
	else
	    mymilli = vals[i] - vals[i-1];
	if ( mymilli<avgmilli*9/10 || mymilli>avgmilli*11/10 )
	    fprintf(stderr,"In file %s:%ld, value of 'set-ends' attribute to <set-bounds> tag (%ld) time to last (%d) is more than 10%% outside average value %d (%s).\n", filename, xmlGetLineNo(sbNode), vals[i], mymilli, avgmilli, (char *) temp );
    }
    md->minorset_millibars = (int) (MILLIBARS_MUL * set_bars);
    md->intro_start = intro_start;
    md->intro_end   = intro_end;
    md->final_end   = final_end;
    md->set_count   = cnt;
    md->set_ends    = vals;
return;
}

static int danceParseMusicSections(const char *filename,MusicDescription *md,xmlNodePtr musicNode,xmlDocPtr doc) {
    /* We allow for the following children
      * intro    -- music introduction (does not need to match movement)
      * ending   -- final flurish (does not need to match movement)
      * minorset -- enough music for one minor set, may need to be slowed down/sped up
      * lastminorset -- enough music for one minor set, play it for the last minor set (to sound like an ending)
      * middlesets -- enough music for an integral number of minorsets
      * body     -- enough music for the entire dance.
     */
    xmlChar *temp;
    xmlNodePtr child;
    MusicSegment **ptr;
    int repeat_as_needed;

    for ( child = musicNode->children; child!=NULL; child=child->next ) {
	if ( child->type!=XML_ELEMENT_NODE )
    continue;
	if ( xmlStrcmp(child->name,(xmlChar *) "intro" )==0 )
	    ptr = &md->intro;
	else if ( xmlStrcmp(child->name,(xmlChar *) "honour" )==0 )
	    ptr = &md->honour;
	else if ( xmlStrcmp(child->name,(xmlChar *) "honour-chord" )==0 )
	    ptr = &md->honour;
	else if ( xmlStrcmp(child->name,(xmlChar *) "ending" )==0 )
	    ptr = &md->ending;
	else if ( xmlStrcmp(child->name,(xmlChar *) "minorset" )==0 )
	    ptr = &md->minorset;
	else if ( xmlStrcmp(child->name,(xmlChar *) "lastminorset" )==0 )
	    ptr = &md->lastminorset;
	else if ( xmlStrcmp(child->name,(xmlChar *) "body" )==0 )
	    ptr = &md->body;
	else if ( xmlStrcmp(child->name,(xmlChar *) "middlesets" )==0 )
	    ptr = &md->middlesets;
	else if ( xmlStrcmp(child->name,(xmlChar *) "set-bounds" )==0 ) {
	    danceParseSetBounds(filename, md,child);
    continue;
	} else if ( xmlStrcmp(child->name,(xmlChar *) "html-notes" )==0 ) {
	    md->html_notes = (char *) xmlNodeListGetEverything(doc,child->children);
    continue;
	} else {
	    setError("In file %s:%ld, unexpected element under music '%s' (must be: intro, ending, minorset, or body).", filename, xmlGetLineNo(child), temp );
    continue;
	}

	if ( *ptr!=NULL ) {
	    setError("In file %s:%ld, %s under music is specified twice.", filename, xmlGetLineNo(child), (char *) child->name );
    continue;
	} else if (( ptr==&md->minorset && md->body!=NULL ) ||
		    ( ptr==&md->body && md->minorset!=NULL )) {
	    setError("In file %s:%ld, you many not specify both a minorset and a body under music.", filename, xmlGetLineNo(child), (char *) child->name );
    continue;
	}

	repeat_as_needed = false;
	temp = xmlGetProp(child,(xmlChar *) "repeats");
	if ( temp==NULL )
	    temp = xmlGetProp(child,(xmlChar *) "repeat");
	if ( temp!=NULL && strcmp((char *) temp,"as needed")==0 ) {
	    repeat_as_needed = true;
	} else if ( ptr==&md->body ) {
	    if ( temp!=NULL ) {
		md->body_repeats = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else
		md->body_repeats = 1;
	} else if ( temp!=NULL ) {
	    setError("In file %s:%ld, only the body (music element) can specify a repeat count.", filename, xmlGetLineNo(child) );
	    xmlFree(temp);
	}

	temp = xmlGetProp(child,(xmlChar *) "count");
	if ( ptr==&md->middlesets ) {
	    if ( temp!=NULL ) {
		md->middleset_count = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else
		md->middleset_count = 1;
	} else if ( temp!=NULL ) {
	    setError("In file %s:%ld, only the middlesets (music element) can specify a repeat count.", filename, xmlGetLineNo(child) );
	    xmlFree(temp);
	}

	*ptr = danceParseMusicSegments(filename,md,child);
	if ( *ptr!=NULL )
	    (*ptr)->repeat_as_needed = repeat_as_needed;
    }
    if ( md->minorset!=NULL && (md->lastminorset!=NULL || md->ending!=NULL) && md->middlesets==NULL )
	danceMakeDefaultMiddleSet(md);
return( 0 );
}

static int danceParseMusicDescription(const char *filename,MusicDescription *md,xmlNodePtr musicNode,xmlDocPtr doc) {
    xmlChar *temp;
    int ret;

    md->filename = (char *) xmlGetProp(musicNode,(xmlChar *) "file");
    temp = xmlGetProp(musicNode,(xmlChar *) "amplify");
    if ( temp!=NULL ) {
	md->amplify = strtod((char *) temp,NULL);
	xmlFree(temp);
    } else
	md->amplify = 1.0;
    temp = xmlGetProp(musicNode,(xmlChar *) "delay-start-bar");
    if ( temp!=NULL ) {
	md->start_millibars =  round( MILLIBARS_MUL * strtod((char *) temp,NULL));
	xmlFree(temp);
    } else
	md->start_millibars = 0;

    /* metadata */
    md->name = (char *) xmlGetProp(musicNode,(xmlChar *) "name");
    if ( md->name==NULL )
	md->name = (char *) xmlGetProp(musicNode,(xmlChar *) "id");
    md->danceName = (char *) xmlGetProp(musicNode,(xmlChar *) "dance-name");
    md->composer = (char *) xmlGetProp(musicNode,(xmlChar *) "composer");
    md->performers = (char *) xmlGetProp(musicNode,(xmlChar *) "performers");
    md->publication = (char *) xmlGetProp(musicNode,(xmlChar *) "publication");
    md->album = (char *) xmlGetProp(musicNode,(xmlChar *) "album");
    md->titleAudioFilename = (char *) xmlGetProp(musicNode,(xmlChar *) "title-audio");
    md->cdImage = (char *) xmlGetProp(musicNode,(xmlChar *) "cd-image");
    md->cdNotes = (char *) xmlGetProp(musicNode,(xmlChar *) "cd-notes");
    md->permission = (char *) xmlGetProp(musicNode,(xmlChar *) "permission");
    md->key = (char *) xmlGetProp(musicNode,(xmlChar *) "key");
    temp = xmlGetProp(musicNode,(xmlChar *) "year-written");
    if ( temp!=NULL ) {
	if ( *temp=='~' )
	    md->year_written = -strtol((char *) temp+1,NULL,10);
	else
	    md->year_written = strtol((char *) temp,NULL,10);
	xmlFree(temp);
	if (( md->year_written>0 && md->year_written<1850 ) ||
		( md->year_written<0 && -md->year_written<1850 ) ) {
	    char *pt = md->name;
	    if ( pt!=NULL ) while ( *pt ) {
		if ( *pt=='s' ) {
		    if ( pt[1]=='\0' || !isalpha(pt[1]) )
			/* Terminal s. Fine as a short s */;
		}
		++pt;
	    }
	}
    }
    temp = xmlGetProp(musicNode,(xmlChar *) "year-performed");
    if ( temp!=NULL ) {
	md->year_performed = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }

    temp = xmlGetProp(musicNode,(xmlChar *) "style");
    if ( temp!=NULL ) {
	md->style = danceMatchEnum(styleNames,(char *) temp);
	if ( md->style==-1 && strcasecmp((char *) temp,"Engliſh Country Dance")==0 )
	    md->style=ds_english;
	else if ( md->style==-1 && strcasecmp((char *) temp,"Contredanſe")==0 )
	    md->style=ds_contredanse;
    } else
	md->style = -1;
    temp = xmlGetProp(musicNode,(xmlChar *) "type");
    if ( temp!=NULL )
	md->music_type = danceMatchEnum(musicTypes,(char *) temp);
    else
	md->music_type = -1;
    temp = xmlGetProp(musicNode,(xmlChar *) "counts-per-bar");
    if ( temp!=NULL )
	md->counts_per_bar = strtol((char *) temp,NULL,10);
    else
	md->counts_per_bar = 2;

    temp = xmlGetProp(musicNode,(xmlChar *) "show-minor-set");
    if ( temp==NULL )
	temp = xmlGetProp(musicNode,(xmlChar *) "show-minorset");
    if ( temp!=NULL ) {
	if ( strcasecmp((char *) temp,"yes")==0 || strcasecmp((char *) temp,"on")==0 ||
		strcasecmp((char *) temp,"true")==0 )
	    md->show_minorset = true;
	else if ( strcasecmp((char *) temp,"no")==0 || strcasecmp((char *) temp,"off")==0 ||
		strcasecmp((char *) temp,"false")==0 )
	    md->show_minorset = false;
	else if ( isdigit(*temp))
	    md->show_minorset = strtol((char *) temp,NULL,10);
	else
	    fprintf( stderr, "Unknown value for 'show-minor-set' \"%s\".\n", temp );
	xmlFree(temp);
    }
    temp = xmlGetProp(musicNode,(xmlChar *) "repetitions");
    if ( temp==NULL )
	temp = xmlGetProp(musicNode,(xmlChar *) "minorset-count");
    if ( temp==NULL )
	temp = xmlGetProp(musicNode,(xmlChar *) "minor-set-count");
    if ( temp!=NULL ) {
	md->desired_repetitions = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }

    ret = danceParseMusicSections(filename,md,musicNode,doc);
    if ( ret==0 && md->set_ends==NULL ) {
	if ( md->intro==NULL && md->minorset!=NULL && md->ending!=NULL &&
		md->minorset->end == md->ending->start ) {
	    /* Colin Hume has a lot of these, the tune goes once through */
	    md->set_ends=calloc(1,sizeof(long));
	    md->set_count = 1;
	    md->set_ends[0] = md->minorset->end;
	    md->minorset_millibars = md->minorset->duration_millibars;
	    md->intro_start = md->intro_end = md->minorset->start;
	    md->final_end = md->ending->end;
	}
    }
return(ret);    
}

static Figure *danceParseFigureNode(const char *filename,xmlDocPtr doc,xmlNodePtr figureNode) {
    xmlNodePtr child;
    int ccnt=0, pcnt=0, rcnt=0, mcnt=0, fcnt=0, compCnt=0, choreoCnt=0, bbcnt=0, icnt=0;
    int ci, pi, ri, compi, choreoi;
    Figure *ret;
    xmlChar *temp;
    int masks=0;
    int oparts_set=0;

    ret = calloc(1,sizeof(Figure));
    ret->is_figure = ( xmlStrcmp(figureNode->name,(xmlChar *) "figure" )==0 );
    ret->is_component = ( xmlStrcmp(figureNode->name,(xmlChar *) "component" )==0 );
    ret->use_long_s = gxmlGetBooleanProp(figureNode,"use-long-s", NULL, 2);
    ret->name = (char *) xmlGetProp(figureNode,(xmlChar *) "id");
    ret->sortName = (char *) xmlGetProp(figureNode,(xmlChar *) "sort-name");
    ret->commonName = (char *) xmlGetProp(figureNode,(xmlChar *) "common-name");
    ret->altName = (char *) xmlGetProp(figureNode,(xmlChar *) "alt-name");
    ret->callGroup = (char *) xmlGetProp(figureNode,(xmlChar *) "call-group");
    ret->author = (char *) xmlGetProp(figureNode,(xmlChar *) "devisor");
    if ( ret->author==NULL )
	ret->author = (char *) xmlGetProp(figureNode,(xmlChar *) "author");
    ret->interpreter = (char *) xmlGetProp(figureNode,(xmlChar *) "interpreter");
    if ( ret->interpreter==NULL )
	ret->interpreter = (char *) xmlGetProp(figureNode,(xmlChar *) "adapted-by");
    ret->animator = (char *) xmlGetProp(figureNode,(xmlChar *) "animator");
    ret->animation_date = (char *) xmlGetProp(figureNode,(xmlChar *) "animation-date");
    ret->source = (char *) xmlGetProp(figureNode,(xmlChar *) "source");
    ret->music_name = (char *) xmlGetProp(figureNode,(xmlChar *) "music");
    ret->license = gxmlGetEmptyProp(figureNode, "license");
    ret->tags = gxmlGetEmptyProp(figureNode, "tags");
    if ( ret->tags==NULL ) {
	ret->tags = gxmlGetEmptyProp(figureNode, "tag");
	if ( ret->tags!=NULL )
	    fprintf( stderr, "%s uses attribute 'tag' instead of 'tags'.\n", ret->name );
    }
    ret->rscds_precis = (char *) xmlGetProp(figureNode,(xmlChar *) "rscds-precis");
    ret->music_author = (char *) xmlGetProp(figureNode,(xmlChar *) "music-author");
    ret->titleAudioFilename = (char *) xmlGetProp(figureNode,(xmlChar *) "title-audio");
    ret->background_file = (char *) xmlGetProp(figureNode,(xmlChar *) "background");
    ret->title_background_file = (char *) xmlGetProp(figureNode,(xmlChar *) "title-background");
    ret->title_font = (char *) xmlGetProp(figureNode,(xmlChar *) "title-font");
    ret->url = gxmlGetEmptyProp(figureNode, "url");
    ret->url10 = gxmlGetEmptyProp(figureNode, "url10");
    ret->url11 = gxmlGetEmptyProp(figureNode, "url11");
    ret->source_url = gxmlGetEmptyProp(figureNode, "source-url");
    ret->sicilian_url = gxmlGetEmptyProp(figureNode, "sicilian-url");
    ret->orig_url = gxmlGetEmptyProp(figureNode, "orig-url");
    ret->la_url = gxmlGetEmptyProp(figureNode, "la-url");
    ret->live_urls = gxmlGetEmptyProp(figureNode, "live-urls");
    ret->live_labels = gxmlGetEmptyProp(figureNode, "live-labels");
    ret->publication = gxmlGetEmptyProp(figureNode, "publication");
    ret->interpreterPublication = gxmlGetEmptyProp(figureNode, "interpreter-publication");
    if ( ret->interpreterPublication==NULL )
	ret->interpreterPublication = gxmlGetEmptyProp(figureNode, "adaptor-publication");
    ret->la_caller = gxmlGetEmptyProp(figureNode, "la-caller");
    ret->la_musicians = gxmlGetEmptyProp(figureNode, "la-musicians");
    ret->la_dancers = gxmlGetEmptyProp(figureNode, "la-dancers");
    ret->key = (char *) xmlGetProp(figureNode,(xmlChar *) "key");
    ret->music_description_ids = (char *) xmlGetProp(figureNode,(xmlChar *) "music-description-ids");

    ret->part_cnt = 1;
    ret->minorset1_bar = -1;
    ret->minorset1_end_bar = -1;
    ret->counts_per_bar = 2;
    ret->counts_per_min = 0;
    ret->music.show_minorset = MINORSET_UNSPECIFIED;
    ret->progression_mod = 1;
    ret->progression_offset = 0;
    temp = xmlGetProp(figureNode,(xmlChar *) "style");
    if ( temp!=NULL ) {
	ret->style = danceMatchEnum(styleNames,(char *) temp);
	if ( ret->style==-1 && strcasecmp((char *) temp,"Engliſh Country Dance")==0 )
	    ret->style=ds_english;
	else if ( ret->style==-1 && strcasecmp((char *) temp,"Contredanſe")==0 )
	    ret->style=ds_contredanse;
	if ( ret->style==-1 ) {
	    setError("In figure %s in file %s:%ld, bad style %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "substyle");
    if ( temp!=NULL ) {
	ret->substyle = danceMatchEnum(substyleNames,(char *) temp);
	if ( ret->substyle==-1 ) {
	    setError("In figure %s in file %s:%ld, bad substyle %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "starting-position");
    if ( temp!=NULL ) {
	ret->formation = danceMatchEnum(startFormationNames,(char *) temp);
	if ( (int) ret->formation==-1 ) {
	    setError("In figure %s in file %s:%ld, bad starting formation %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "level");
    if ( temp==NULL )
	temp = xmlGetProp(figureNode,(xmlChar *) "difficulty-level");
    if ( temp!=NULL ) {
	ret->difficulty_level = danceMatchEnum(difficultyLevelNames,(char *) temp);
	if ( (int) ret->difficulty_level==-1 ) {
	    setError("In figure %s in file %s:%ld, bad level: %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "improper");
    if ( temp!=NULL ) {
	ret->improper_mask = danceRoleList(roleNames,(char *) temp);
	if ( (int) ret->improper_mask==0xffffffff ) {
	    setError("In figure %s in file %s:%ld, bad list of improper couples %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "becket");
    if ( temp!=NULL ) {
	ret->becket_style = danceMatchEnum(becketStyle,(char *) temp);
	if ( (int) ret->becket_style==0xffffffff ) {
	    setError("In figure %s in file %s:%ld, bad becket information %s (must be cw, ccw or none).", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "progression");
    if ( temp!=NULL ) {
	char *end;
	if ( strcmp( (char *) temp, "old-style")==0 || strcmp( (char *) temp, "old style")==0 ) {
	    ret->old_style_progression = true;
	    ret->progression = 1;
	} else {
	    ret->progression = strtol((char *) temp,&end,10);
	    if ( strcmp(end,",old-style")==0 )
		ret->old_style_progression = true;
	    else if ( strcmp(end,",old style")==0 )
		ret->old_style_progression = true;
	    else if ( *end!='\0' )
		fprintf( stderr, "In file %s:%ld, Junk after number in progression: %s\n",filename, xmlGetLineNo(figureNode), temp );
	}
	xmlFree(temp);
    } else
	ret->progression = 1;
    temp = xmlGetProp(figureNode,(xmlChar *) "original-formation");
    if ( temp!=NULL ) {
	ret->original_formation = danceMatchEnum(startFormationNames,(char *) temp);
	if ( (int) ret->original_formation==-1 ) {
	    setError("In figure %s in file %s:%ld, bad original formation %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    } else
	ret->original_formation = ret->formation;
    temp = xmlGetProp(figureNode,(xmlChar *) "original-improper");
    if ( temp!=NULL ) {
	if ( strcmp((char *) temp,"0")==0 )
	    ret->original_improper = 0;
	else
	    ret->original_improper = danceRoleList(roleNames,(char *) temp);
	if ( (int) ret->improper_mask==(int) 0xffffffff ) {
	    setError("In figure %s in file %s:%ld, bad list of original improper couples %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	xmlFree(temp);
    } else
	ret->original_improper = ret->improper_mask;
    temp = xmlGetProp(figureNode,(xmlChar *) "original-progression");
    if ( temp!=NULL ) {
	char *end;
	ret->original_progression = strtol((char *) temp,&end,10);
	if ( *end!='\0' )
	    fprintf( stderr, "In file %s:%ld, Junk after number in original progression: %s\n",filename, xmlGetLineNo(figureNode), temp );
	xmlFree(temp);
    } else
	ret->original_progression = ret->progression;

    temp = xmlGetProp(figureNode,(xmlChar *) "band-pos");
    if ( temp==NULL )
	temp = xmlGetProp(figureNode,(xmlChar *) "bandPos");
    if ( temp!=NULL ) {
	int pos = danceMatchEnum(bandNames,(char *) temp);
	if ( pos==-1 ) {
	    setError("In figure %s in file %s:%ld, bad band position %s.", ret->name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree(ret);
return( NULL );
	}
	ret->bandPos = pos;
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "year-written");
    if ( temp!=NULL ) {
	if ( *temp=='~' )
	    ret->year_written = -strtol((char *) temp+1,NULL,10);
	else
	    ret->year_written = strtol((char *) temp,NULL,10);
	xmlFree(temp);
/* The Times (London) switchted from the long to the short s on 10 Sept 1803 */
/* Acts of US Congress were printed with the long s up through 1803, but short s in 1804 */
/* In France the switch occured between 1782 and 1793 */
/*  http://en.wikipedia.org/wiki/Long_s */
	if ( ret->style!=ds_scottish &&	/* The scottish font doesn't contain a long s */
		(( ret->year_written>0 && ret->year_written<=1803 ) ||
		 ( ret->year_written<0 && -ret->year_written<=1803 )) ) {
	    char *pt = ret->name;
	    /* I stop a ~ because that is usually a mark that I'm starting the name of the modern interpreter */
	    if ( pt!=NULL ) while ( *pt && *pt!='~' && *pt!='-' ) {
		if ( *pt=='s' ) {
		    if ( pt[1]=='\0' || !isalpha(pt[1]) )
			/* Terminal s. Fine as a short s */;
		    else if ( pt[1]=='f' || (pt>ret->name && pt[-1]=='f' ))
			/* around an "f". Fine as a short s */;
			/* Round s before k and b in the 17th century but not 18th. Ignore that */
		    else if ( pt>ret->name && pt[-1]=='\xbf' )
			/* Two ses together. The first seems to be long and */
			/*  the second short */;
		    else /* if ( strcasecmp(ret->name,"The Hop Ground ~ Laskie?")!=0 &&
				strcasecmp(ret->name,"Mr. Beveridge's Maggot ~ Gibson")!=0 ) */
			fprintf( stderr, "%s contains a short s that probably should be a long s (%s). (%s)\n", ret->name, pt, filename );
		}
		++pt;
	    }
	    pt = ret->author;
	    if ( pt!=NULL ) while ( *pt ) {
		if ( *pt=='s' ) {
		    if ( pt[1]=='\0' || !isalpha(pt[1]) )
			/* Terminal s. Fine as a short s */;
		    else if ( pt[1]=='f' || (pt>ret->name && pt[-1]=='f' ))
			/* around an "f". Fine as a short s */;
			/* Round s before k and b in the 17th century but not 18th. Ignore that */
		    else /* if ( strcasecmp(ret->author,"Castle Menzies")==0 ) */
			fprintf( stderr, "%s contains a short s that probably should be a long s. (%s) in %s\n", ret->author, pt, ret->name );
		}
		++pt;
	    }
	    if ( ret->name!=NULL && (strstr(ret->name,"fſ")!=NULL || strstr(ret->name,"ſf")!=NULL ))
		fprintf( stderr, "In %s there is a long-s&f combination when there shouldn't be\n", ret->name );
	    if ( ret->author!=NULL && (strstr(ret->author,"fſ")!=NULL || strstr(ret->author,"ſf")!=NULL ))
		fprintf( stderr, "In the author of %s there is a long-s&f combination when there shouldn't be\n", ret->name );
	    
	} else {
	    if ( ret->name!=NULL && (strstr(ret->name,"ſ")!=NULL || strstr(ret->name,"ß")!=NULL ))
		fprintf( stderr, "In %s there is a long-s when there shouldn't be\n", ret->name );
	    if ( ret->author!=NULL && (strstr(ret->author,"ſ")!=NULL || strstr(ret->author,"ß")!=NULL ))
		fprintf( stderr, "In the author of %s there is a long-s when there shouldn't be\n", ret->name );
	}
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "year-interpreted");
    if ( temp==NULL )
	temp = xmlGetProp(figureNode,(xmlChar *) "year-adapted");
    if ( temp!=NULL ) {
	if ( *temp=='~' )
	    ret->year_interpreted = -strtol((char *) temp+1,NULL,10);
	else
	    ret->year_interpreted = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "units");
    if ( temp!=NULL ) {
	ret->units = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    } else
	ret->units = 1024;
    temp = xmlGetProp(figureNode,(xmlChar *) "minorset-spacing");
    if ( temp==NULL ) {
	temp = xmlGetProp(figureNode,(xmlChar *) "minor-set-spacing");
	if ( temp!=NULL )
	    fprintf( stderr, "Use 'minorset-spacing' instead.\n" );
    }
    if ( temp!=NULL ) {
	ret->minorset_spacing = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    } else
	ret->minorset_spacing = 0;
    temp = xmlGetProp(figureNode,(xmlChar *) "grid-pos");
    if ( temp!=NULL ) {
	char *pt, *end;
	int cnt;
	cnt=1;
	pt = (char *) temp;
	while ( (pt = strchr(pt+1,';'))!=NULL )
	    ++cnt;
	ret->grid_pos = calloc(cnt+1,sizeof(Position *));

	cnt = 0;
	pt = (char *) temp;
	for ( ;; ) {
	    ret->grid_pos[cnt] = calloc(1,sizeof(Position));
	    ret->grid_pos[cnt]->x = strtod(pt,&end);
	    if ( *end!=',' )
		fprintf( stderr, "In the grid-pos of %s there is a bad number near: %s\n", ret->name, pt );
	    ret->grid_pos[cnt]->y = strtod(end+1,&end);
	    if ( *end=='\0' )
	break;
	    if ( *end!=';' )
		fprintf( stderr, "In the grid-pos of %s positions must be separated by semi-colons near: %s\n", ret->name, pt );
	    pt = end+1;
	    ++cnt;
	}
    }

    ret->stroke_text_on_background = gxmlGetBooleanProp(figureNode,"stroke-text","stroke-text-on-background",0);
    ret->do_honour = honour_default;
    temp = xmlGetProp(figureNode,(xmlChar *) "honour");
    if ( temp==NULL )
	temp = xmlGetProp(figureNode,(xmlChar *) "honor");
    if ( temp!=NULL ) {
	if ( strcasecmp((char *) temp,"yes")==0 || strcasecmp((char *) temp,"on")==0 || strcasecmp((char *) temp,"true")==0 || strcasecmp((char *) temp,"do")==0 || *temp=='1' )
	    ret->do_honour = honour_doit;
	else if ( strcasecmp((char *) temp,"no")==0 || strcasecmp((char *) temp,"off")==0 || strcasecmp((char *) temp,"false")==0 || strcasecmp((char *) temp,"dont")==0 || strcasecmp((char *) temp,"don't")==0 || *temp=='0' )
	    ret->do_honour = honour_dont;
	else
	    fprintf( stderr, "Unknown honour value: %s. Should be yes/no or on/off.\n", (char *) temp);
	xmlFree(temp);
    }
    ret->do_honour_end = honour_default;
    temp = xmlGetProp(figureNode,(xmlChar *) "honour-at-end");
    if ( temp==NULL )
	temp = xmlGetProp(figureNode,(xmlChar *) "honor-at-end");
    if ( temp!=NULL ) {
	if ( strcasecmp((char *) temp,"yes")==0 || strcasecmp((char *) temp,"on")==0  || strcasecmp((char *) temp,"do")==0 || *temp=='1' )
	    ret->do_honour_end = honour_doit;
	else if ( strcasecmp((char *) temp,"no")==0 || strcasecmp((char *) temp,"off")==0  || strcasecmp((char *) temp,"dont")==0 || strcasecmp((char *) temp,"don't")==0 || *temp=='0' )
	    ret->do_honour_end = honour_dont;
	else
	    fprintf( stderr, "Unknown honour value: %s. Should be yes/no or on/off.\n", (char *) temp);
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "counts-per-bar");
    if ( temp!=NULL ) {
	ret->counts_per_bar = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    } else
	ret->counts_per_bar = 2;
    temp = xmlGetProp(figureNode,(xmlChar *) "counts-per-min");
    if ( temp!=NULL ) {
	ret->counts_per_min = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    } else
	ret->counts_per_min = 0;
    temp = xmlGetProp(figureNode,(xmlChar *) "counts-per-sec");
    if ( temp!=NULL ) {
	fprintf( stderr, "I think you meant 'counts-per-min' not 'counts-per-sec'.\n" );
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "parts");
    if ( temp!=NULL ) {
	ret->part_cnt = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "original-parts");
    if ( temp!=NULL ) {
	char *end;
	ret->original_parts = strtol((char *) temp,&end,10);
	if ( *end!='\0' )
	    fprintf( stderr, "In file %s:%ld, Junk after number in original parts: %s\n",filename, xmlGetLineNo(figureNode), temp );
	xmlFree(temp);
	oparts_set = 1;
    } else
	ret->original_parts = ret->part_cnt;
    temp = xmlGetProp(figureNode,(xmlChar *) "repetitions");
    if ( temp==NULL )
	temp = xmlGetProp(figureNode,(xmlChar *) "minorset-count");
    if ( temp==NULL )
	temp = xmlGetProp(figureNode,(xmlChar *) "minor-set-count");
    if ( temp!=NULL ) {
	ret->desired_repetitions = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "couple-count");
    if ( temp!=NULL ) {
	ret->desired_couple_cnt = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }
    ret->show_minorset = gxmlGetBooleanProp(figureNode,"show-minor-set","show-minorset", 1 );
    ret->show_figure_titles = gxmlGetBooleanProp(figureNode,"show-figure-titles",NULL, 0 );
    /* show-component-titles */
    temp = xmlGetProp(figureNode,(xmlChar *) "text-color");
    ret->text_color = -1;
    if ( temp!=NULL && *temp=='#' )
	ret->text_color = strtol((char *) temp+1,NULL,16);
    if ( temp!=NULL )
	xmlFree(temp);
    temp = xmlGetProp(figureNode,(xmlChar *) "progression-rotation-center");
    if ( temp!=NULL ) {
	char *end;
	ret->progression_center.x = strtod((char *) temp,&end);
	if ( *end!=',' ) {
	    fprintf( stderr, "Bad value for 'progression-rotation-center' \"%s\", expected comma here \"%s\".\n", temp, end );
	} else {
	    ret->progression_center.y = strtod(end+1,&end);
	    if ( *end=='r' ) {
		ret->progression_offset = strtol(end+1,&end,10);
		if ( *end!='\0' )
		    fprintf( stderr, "Bad value for 'progression-rotation-center' \"%s\", expected end of text here \"%s\".\n", temp, end );
	    } else if ( *end!='%' ) {
		fprintf( stderr, "Bad value for 'progression-rotation-center' \"%s\", expected percent here \"%s\".\n", temp, end );
	    } else {
		ret->progression_mod = strtol(end+1,&end,10);
		if ( *end!='\0' )
		    fprintf( stderr, "Bad value for 'progression-rotation-center' \"%s\", expected end of text here \"%s\".\n", temp, end );
		if ( ret->progression_mod<1 ) {
		    fprintf( stderr, "Bad value for 'progression-rotation-center' \"%s\", final (mod) value must be bigger than 1.\n", temp );
		    ret->progression_mod = 1;
		}
	    }
	}
	xmlFree(temp);
    }
    temp = xmlGetProp(figureNode,(xmlChar *) "ignore-sex");
    if ( temp!=NULL ) {
	if ( strcasecmp((char *) temp,"true")==0 || strcmp((char *) temp,"1")==0 )
	    ret->ignore_sex = 1;
	xmlFree(temp);
    }

    ccnt=pcnt=rcnt=fcnt=mcnt=icnt=compCnt=choreoCnt=bbcnt=0;
    for ( child = figureNode->children; child!=NULL; child=child->next ) {
	if ( child->type!=XML_ELEMENT_NODE )
    continue;
	if ( xmlStrcmp(child->name,(xmlChar *) "call-name" )==0 )
	    ++ccnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "bbs" )==0 )
	    ++bbcnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "person" )==0 )
	    ++pcnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "role" )==0 )
	    ++rcnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "component" )==0 )
	    ++compCnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "choreography" )==0 )
	    ++choreoCnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "face" )==0 )
	    ++fcnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "music" )==0 )
	    ++mcnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "music-include" )==0 )
	    ++icnt;
	else if ( xmlStrcmp(child->name,(xmlChar *) "if" )==0 ||
		xmlStrcmp(child->name,(xmlChar *) "elseif" )==0 ||
		xmlStrcmp(child->name,(xmlChar *) "else" )==0 ) {
	    xmlNodePtr grandchild;
	    for ( grandchild=child->children; grandchild!=NULL; grandchild=grandchild->next ) {
		if ( xmlStrcmp(grandchild->name,(xmlChar *) "call-name" )==0 )
		    ++ccnt;
	    }
	} else if ( xmlStrcmp(child->name,(xmlChar *) "notes" )!= 0 &&
		  xmlStrcmp(child->name,(xmlChar *) "html-notes" )!= 0 &&
		  xmlStrcmp(child->name,(xmlChar *) "parts" )!= 0 &&
		  xmlStrcmp(child->name,(xmlChar *) "minorset1" )!= 0 &&
		  xmlStrcmp(child->name,(xmlChar *) "whirl" )!= 0 &&
		  child->type!= XML_TEXT_NODE &&
		  child->type !=  XML_COMMENT_NODE ) {
	    fprintf(stderr,"In figure %s in file %s:%ld, Unrecognized child of dance/figure: <%s\n", ret->name, filename, xmlGetLineNo(child), child->name );
	}
    }
    ret->people = pcnt;
    ret->role_cnt = rcnt;
    ret->call_cnt = ccnt;
    ret->comp_cnt = compCnt;
    ret->choreo_cnt = choreoCnt;

    if ( bbcnt>1 ) {
	fprintf(stderr,"In figure %s in file %s:%ld, Too many bounding box elements: <%s\n", ret->name, filename, xmlGetLineNo(figureNode), figureNode->name );
    }
    if ( mcnt>1 ) {
	fprintf(stderr,"In figure %s in file %s:%ld, Too many <music> elements: <%s\n", ret->name, filename, xmlGetLineNo(figureNode), figureNode->name );
    }
    if ( icnt>1 ) {
	fprintf(stderr,"In figure %s in file %s:%ld, Too many <music-include/> elements: <%s\n", ret->name, filename, xmlGetLineNo(figureNode), figureNode->name );
    }
    if ( mcnt>0 && icnt>0 ) {
	fprintf(stderr,"In figure %s in file %s:%ld, Only specify one of <music> and <music-include/> elements: <%s\n", ret->name, filename, xmlGetLineNo(figureNode), figureNode->name );
    }

    if ( ccnt!=0 )
	ret->calls = calloc(ccnt+1,sizeof(CallList));
    if ( rcnt!=0 )
	ret->roles = calloc(rcnt+1,sizeof(Person));
    if ( pcnt!=0 )
	ret->dancers = calloc(pcnt+1,sizeof(Person));
    if ( compCnt!=0 )
	ret->components = calloc(compCnt+1,sizeof(Dance *));
    if ( choreoCnt!=0 )
	ret->sequence = calloc(choreoCnt+1,sizeof(Choreography));
    if ( fcnt!=0 ) {
	int i;
	ret->face_cnt = 0;
	ret->face_max = fcnt;
	ret->faceImages = calloc(fcnt,sizeof(struct faceImage));;
	for ( i=0; i<fcnt; ++i ) {
	    ret->faceImages[i].person    = -1;
	    ret->faceImages[i].sex       = s_unknown;
	    ret->faceImages[i].facescale = 1;
	}
    }

    ci = ri = pi = compi = choreoi = 0;
    for ( child = figureNode->children; child!=NULL; child=child->next ) {
	if ( xmlStrcmp(child->name,(xmlChar *) "call-name" )==0 ) {
	    ci = parseCallName(filename,child,ret,ci,-1);
	    if ( ci==-1 )
return( NULL );
	    masks = 0;
	} else if ( xmlStrcmp(child->name,(xmlChar *) "if" )==0 ||
		xmlStrcmp(child->name,(xmlChar *) "elseif" )==0 ||
		xmlStrcmp(child->name,(xmlChar *) "else" )==0 ) {
	    if ( xmlStrcmp(child->name,(xmlChar *) "if" )==0 ) {
		if ( masks!=0 ) {
		    setError("In figure %s in file %s:%ld, two 'if's without an 'else'.", ret->name, filename, xmlGetLineNo(child), temp );
		    figureFree(ret);
return( NULL );
		}
		masks = 0;
	    } else {
	    	if ( masks==0 ) {
		    setError("In figure %s in file %s:%ld, else/elseif without an 'if'.", ret->name, filename, xmlGetLineNo(child), temp );
		    figureFree(ret);
return( NULL );
	    	}
	    }
	    ci = parseCallIf(filename,child,ret,ci,&masks);
	    if ( ci==-1 )
return( NULL );
	    if ( xmlStrcmp(child->name,(xmlChar *) "else" )==0 )
		masks = 0;
	} else if ( xmlStrcmp(child->name,(xmlChar *) "bbs" )==0 ) {
	    ret = parseBBs(ret,child,filename);
	    if ( ret==NULL )
return( NULL );
	} else if ( xmlStrcmp(child->name,(xmlChar *) "music" )==0 ) {
	    danceParseMusicDescription(filename,&ret->music,child,doc);
	} else if ( xmlStrcmp(child->name,(xmlChar *) "music-include" )==0 ) {
	    MusicDescription *md;
	    xmlChar *tmp;
	    ret->include_md = (char *) xmlGetProp(child,(xmlChar *) "filename");
	    if ( ret->include_md==NULL ) {
		setError("In figure %s in file %s:%ld, <music-include/> must specify a filename.", ret->name, filename, xmlGetLineNo(figureNode), temp );
		xmlFree(temp);
		figureFree(ret);
return( NULL );
	    }
	    md = danceLoadMusicDescription(ret->include_md);
	    if ( md==NULL ) {
		setError("In figure %s in file %s:%ld, Failed to load a music description from %s.", ret->name, filename, xmlGetLineNo(figureNode), temp, ret->include_md );
		xmlFree(temp);
		figureFree(ret);
return( NULL );
	    }
	    ret->music = *md;
	    free(md);
	    tmp = xmlGetProp(child,(xmlChar *) "title-audio");
	    if ( tmp!=NULL ) {
		free( ret->music.titleAudioFilename );
		ret->music.titleAudioFilename = (char *) tmp;
	    }
	} else if ( xmlStrcmp(child->name,(xmlChar *) "person" )==0 ) {
	    if ( danceParsePerson(filename,doc,child, &ret->dancers[pi++], ret) == NULL ) {
		figureFree(ret);
return( NULL );
	    }
	} else if ( xmlStrcmp(child->name,(xmlChar *) "role" )==0 ) {
	    if ( danceParsePerson(filename,doc,child, &ret->roles[ri++], ret)==NULL ) {
		figureFree(ret);
return( NULL );
	    }
	} else if ( xmlStrcmp(child->name,(xmlChar *) "notes" )==0 ) {
	    ret->notes = (char *) xmlNodeListGetEverything(doc,child->children);
	} else if ( xmlStrcmp(child->name,(xmlChar *) "html-notes" )==0 ) {
	    ret->html_notes = (char *) xmlNodeListGetEverything(doc,child->children);
	} else if ( xmlStrcmp(child->name,(xmlChar *) "parts" )==0 ) {
	    char *temp = (char *) xmlNodeListGetString(doc,child->children,true);
	    ret->part_cnt = strtol(temp,NULL,10);
	    if ( ret->part_cnt<=0 || ret->part_cnt>32 ) {
		setError("In figure %s in file %s:%ld, bad number of parts must be between 1 and 32: \"%s\".", ret->name, filename, xmlGetLineNo(child), temp );
		free(temp);
		figureFree(ret);
return( NULL );
	    }
	    free(temp);
	    if ( !oparts_set )
		ret->original_parts = ret->part_cnt;
	} else if ( xmlStrcmp(child->name,(xmlChar *) "face" )==0 ) {
	    double scale;
	    xmlChar *stemp;
	    temp = xmlGetProp(child,(xmlChar *) "person");
	    stemp = xmlGetProp(child,(xmlChar *) "sex");
	    if ( temp==NULL && stemp==NULL ) {
		setError("In figure %s in file %s:%ld, no person nor sex for a face.", ret->name, filename, xmlGetLineNo(child) );
	    } else if ( temp!=NULL && stemp!=NULL ) {
		setError("In figure %s in file %s:%ld, both person and sex for a face.", ret->name, filename, xmlGetLineNo(child) );
		xmlFree(temp);
		xmlFree(stemp);
	    } else {
		struct faceImage *fi = &ret->faceImages[ret->face_cnt++];
		if ( temp!=NULL ) {
		    fi->person = strtol((char *) temp,NULL,10);
		    xmlFree(temp);
		} else if ( stemp!=NULL ) {
		    fi->sex = parseSex(stemp,filename,child);
		    xmlFree(stemp);
		}
		fi->facename = (char *) xmlGetProp(child,(xmlChar *) "filename");
		temp = xmlGetProp(child,(xmlChar *) "scale");
		if ( temp!=NULL ) {
		    scale = strtod((char *) temp,NULL);
		    if ( scale==0 )
			scale = 1.;
		    fi->facescale = scale;
		}
		temp = xmlGetProp(child,(xmlChar *) "hide-normal");
		if ( temp!=NULL ) {
		    int val = strtol((char *) temp,NULL,10);
		    fi->hide_normal = val!=0;
		}
	    }
	} else if ( xmlStrcmp(child->name,(xmlChar *) "minorset1" )==0 ) {
	    if ( ret->minorset1_bar!=-1 )
		setError("In figure %s in file %s:%ld, multiple minorset1 tags.", ret->name, filename, xmlGetLineNo(child) );
	    temp = xmlGetProp(child,(xmlChar *) "bar");
	    if ( temp==NULL )
		temp = xmlGetProp(child,(xmlChar *) "beat");
	    if ( temp!=NULL ) {
		ret->minorset1_bar = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else
		setError("In figure %s in file %s:%ld, no bar for a minorset.", ret->name, filename, xmlGetLineNo(child) );
	    temp = xmlGetProp(child,(xmlChar *) "dont-show-couples-above-me");
	    if ( temp!=NULL ) {
		ret->minorset1_dont_show_couples_above_me = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else
		setError("In figure %s in file %s:%ld, no dont-show-couples-above-me for a minorset.", ret->name, filename, xmlGetLineNo(child) );
	    temp = xmlGetProp(child,(xmlChar *) "end-bar");
	    if ( temp==NULL )
		temp = xmlGetProp(child,(xmlChar *) "end-beat");
	    if ( temp!=NULL ) {
		ret->minorset1_end_bar = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    }
	    if ( xmlHasProp(child,(xmlChar *) "same-bb") )
		ret->minorset1_same_bb = true;
	} else if ( xmlStrcmp(child->name,(xmlChar *) "whirl" )==0 ) {
	    temp = xmlGetProp(child,(xmlChar *) "bar");
	    if ( temp==NULL )
		temp = xmlGetProp(child,(xmlChar *) "beat");
	    if ( temp!=NULL ) {
		ret->whirl_bar = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else
		setError("In figure %s in file %s:%ld, no bar for a whirl.", ret->name, filename, xmlGetLineNo(child) );
	    temp = xmlGetProp(child,(xmlChar *) "end-bar");
	    if ( temp==NULL )
		temp = xmlGetProp(child,(xmlChar *) "end-beat");
	    if ( temp!=NULL ) {
		ret->whirl_end_bar = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    }
	} else if ( xmlStrcmp(child->name,(xmlChar *) "component" )==0 ) {
	    ret->components[compi++] = danceParseFigureNode(filename,doc,child);
	    if ( ret->components[compi-1]==NULL && lastError==NULL )
		setError("In figure %s in file %s:%ld, bad component.", ret->name, filename, xmlGetLineNo(child) );
	} else if ( xmlStrcmp(child->name,(xmlChar *) "choreography" )==0 ) {
	    /* Deal with these on the next pass */;
	}
    }
    for ( child = figureNode->children; child!=NULL; child=child->next ) {
	if ( xmlStrcmp(child->name,(xmlChar *) "choreography" )==0 ) {
	    temp = xmlGetProp(child,(xmlChar *) "active");
	    if ( temp!=NULL ) {
		ret->sequence[choreoi].active = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "part");
	    if ( temp!=NULL ) {
		ret->sequence[choreoi].part = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    }
	    temp = xmlGetProp(child,(xmlChar *) "current-ms");
	    if ( temp!=NULL ) {
		ret->sequence[choreoi].current_ms = strtol((char *) temp,NULL,10);
		xmlFree(temp);
	    } else
		ret->sequence[choreoi].current_ms = -1;

	    if ( xmlHasProp(child, (xmlChar *) "no-one-out-at-top" ) )
		ret->sequence[choreoi].no_one_out_at_top = true;
	    if ( xmlHasProp(child, (xmlChar *) "clockwise" ) )
		ret->sequence[choreoi].clockwise = true;
	    if ( xmlHasProp(child, (xmlChar *) "first-set" ) )
		ret->sequence[choreoi].first_set = true;
	    temp = xmlGetProp(child,(xmlChar *) "figure");
	    if ( temp==NULL ) {
		setError("In figure %s in file %s:%ld, no figure specified in choreography.", ret->name, filename, xmlGetLineNo(child) );
		figureFree(ret);
		xmlFree(temp);
return( NULL );
	    } else if (( ret->sequence[choreoi].figure = danceFindFigure(ret,(char *) temp))== NULL ) {
		setError("In figure %s in file %s:%ld, no figure named: %s.", ret->name, filename, xmlGetLineNo(child), (char *) temp );
		figureFree(ret);
		xmlFree(temp);
return( NULL );
	    }
	    xmlFree(temp);
	    temp = xmlGetProp(child,(xmlChar *) "time-scale");
	    ret->sequence[choreoi].time_scale = 1.0;
	    if ( temp!=NULL ) {
		ret->sequence[choreoi].time_scale = strtod((char *) temp,NULL);
		xmlFree(temp);
	    }
	    if ( ret->sequence[choreoi].figure!=NULL )
		++choreoi;
	}
    }
    ret->choreo_cnt = choreoi;
	
    ret->roleMask = 0;
    for ( ri=0; ri<ret->role_cnt; ++ri )
	ret->roleMask |= (1L<<ret->roles[ri].effective_role);
return( ret );
}

static Figure *danceParseComparisonNode(const char *filename,xmlDocPtr doc,xmlNodePtr figureNode) {
    xmlNodePtr child;
    Comparison *ret;
    xmlChar *temp;
    int i;

    ret = calloc(1,sizeof(Comparison));
    ret->dance.is_comparison = true;
    ret->dance.name = (char *) xmlGetProp(figureNode,(xmlChar *) "id");
    ret->dance.sortName = (char *) xmlGetProp(figureNode,(xmlChar *) "sort-name");
    ret->dance.commonName = (char *) xmlGetProp(figureNode,(xmlChar *) "common-name");
    ret->dance.author = (char *) xmlGetProp(figureNode,(xmlChar *) "author");
    ret->dance.animator = (char *) xmlGetProp(figureNode,(xmlChar *) "animator");
    ret->dance.animation_date = (char *) xmlGetProp(figureNode,(xmlChar *) "animation-date");
    ret->dance.publication = (char *) xmlGetProp(figureNode,(xmlChar *) "publication");
    ret->dance.titleAudioFilename = (char *) xmlGetProp(figureNode,(xmlChar *) "title-audio");
    ret->dance.url = (char *) xmlGetProp(figureNode,(xmlChar *) "url");
    temp = xmlGetProp(figureNode,(xmlChar *) "year-written");
    if ( temp!=NULL ) {
	if ( *temp=='~' )
	    ret->dance.year_written = -strtol((char *) temp+1,NULL,10);
	else
	    ret->dance.year_written = strtol((char *) temp,NULL,10);
	xmlFree(temp);
    }

    for ( i=0; i<2; ++i ) {
	char buffer[64];
	FigureLib *fig;
	sprintf( buffer, "dance%d", i+1 );
	ret->dances[i].filename = (char *) xmlGetProp(figureNode,(xmlChar *) buffer);
	if ( ret->dances[i].filename == NULL ) {
	    fprintf( stderr, "No filename given for %s\n", buffer );
	    free(ret);
return( NULL );
	}
	sprintf( buffer, "args%d", i+1 );
	ret->dances[i].args = (char *) xmlGetProp(figureNode,(xmlChar *) buffer);
	if ( ret->dances[i].args==NULL )
	    ret->dances[i].args = strdup("");
	ret->dances[i].sicilian = 2;	/* unspecified */
	sprintf( buffer, "sicilian%d", i+1 );
	temp = xmlGetProp(figureNode,(xmlChar *) buffer);
	if ( temp!=NULL && (strcmp((char *) temp,"on")==0 || strcmp((char *)temp, "true")==0 || strcmp((char *)temp, "yes")==0) )
	    ret->dances[i].sicilian = true;
	else if ( temp!=NULL && (strcmp((char *) temp,"off")==0 || strcmp((char *)temp, "false")==0 || strcmp((char *)temp, "no")==0) )
	    ret->dances[i].sicilian = false;
	else if ( temp!=NULL )
	    fprintf( stderr, "Unknown value for %s: %s\n", buffer, (char *)temp);
	xmlFree(temp);

	fig = danceLoadLibrary(ret->dances[i].filename);
	if ( fig==NULL || fig->figures==NULL || fig->figures[0]==NULL ) {
	    fprintf( stderr, "Nothing in %s\n", ret->dances[i].filename );
	    free(ret);
return( NULL );
	}
	ret->dances[i].d = fig->figures[0];
    }

    ret->sourceImage = (char *) xmlGetProp(figureNode,(xmlChar *) "source-image");
    ret->imageCopyright = (char *) xmlGetProp(figureNode,(xmlChar *) "image-copyright");
    temp = xmlGetProp(figureNode,(xmlChar *) "stacked");
    if ( temp!=NULL && (strcmp((char *) temp,"vertically")==0 || strcmp((char *)temp, "v")==0) )
	ret->stacked_vertically = true;
    else if ( temp!=NULL && (strcmp((char *) temp,"horizontally")==0 || strcmp((char *)temp, "h")==0) )
	ret->stacked_vertically = false;
    else if ( temp!=NULL )
	fprintf( stderr, "Unknown value for stacked: %s\n", (char *)temp);
    else {
	ret->stacked_vertically = true;
	if (( ret->dances[0].d->formation==sp_squaredSet ||
		(ret->dances[0].d->formation>=sp_facingCouples &&
		    ret->dances[0].d->formation<=sp_facingCouplesBecket) ||
		(ret->dances[0].d->formation>=sp_3CplCircle &&
		    ret->dances[0].d->formation<=sp_7CplCircle) ||
		ret->dances[0].d->formation==sp_square3 ||
		(ret->dances[0].d->formation>=sp_circularMonople &&
		    ret->dances[0].d->formation<=sp_trioCircularMonople &&
		    ret->dances[0].sicilian!=0 )) &&
	    ( ret->dances[1].d->formation==sp_squaredSet ||
		(ret->dances[1].d->formation>=sp_facingCouples &&
		    ret->dances[1].d->formation<=sp_facingCouplesBecket) ||
		(ret->dances[1].d->formation>=sp_3CplCircle &&
		    ret->dances[1].d->formation<=sp_7CplCircle) ||
		ret->dances[1].d->formation==sp_square3 ||
		(ret->dances[1].d->formation>=sp_circularMonople &&
		    ret->dances[1].d->formation<=sp_trioCircularMonople &&
		    ret->dances[1].sicilian!=0 )) )
	ret->stacked_vertically = false;
    }
    xmlFree(temp);

    for ( child = figureNode->children; child!=NULL; child=child->next ) {
	if ( xmlStrcmp(child->name,(xmlChar *) "html-notes" )==0 )
	    ret->dance.html_notes = (char *) xmlNodeListGetEverything(doc,child->children);
	if ( xmlStrcmp(child->name,(xmlChar *) "music-html-notes" )==0 )
	    ret->dance.music.html_notes = (char *) xmlNodeListGetEverything(doc,child->children);
    }

    temp = xmlGetProp(figureNode,(xmlChar *) "style");
    if ( temp!=NULL ) {
	ret->dance.style = danceMatchEnum(styleNames,(char *) temp);
	if ( ret->dance.style==-1 && strcasecmp((char *) temp,"Engliſh Country Dance")==0 )
	    ret->dance.style=ds_english;
	else if ( ret->dance.style==-1 && strcasecmp((char *) temp,"Contredanſe")==0 )
	    ret->dance.style=ds_contredanse;
	if ( ret->dance.style==-1 ) {
	    setError("In figure %s in file %s:%ld, bad style %s.", ret->dance.name, filename, xmlGetLineNo(figureNode), temp );
	    xmlFree(temp);
	    figureFree((Dance *) ret);
return( NULL );
	}
	xmlFree(temp);
    } else
	ret->dance.style = ret->dances[0].d->style;

    if ( ret->dance.year_written==0 && ret->dances[0].d->year_written==ret->dances[1].d->year_written )
	ret->dance.year_written = ret->dances[0].d->year_written;
    if ( ret->dance.author==NULL && ret->dances[0].d->author!=NULL &&
	    ret->dances[1].d->author!=NULL && strcmp(ret->dances[1].d->author,ret->dances[0].d->author)==0 )
	ret->dance.author = strdup(ret->dances[0].d->author);
    if ( ret->dance.publication==NULL && ret->dances[0].d->publication!=NULL &&
	    ret->dances[1].d->publication!=NULL && strcmp(ret->dances[1].d->publication,ret->dances[0].d->publication)==0 )
	ret->dance.publication = strdup(ret->dances[0].d->publication);
    if ( ret->dance.music.html_notes==NULL && ret->dances[0].d->music.html_notes!=NULL &&
	    ret->dances[1].d->music.html_notes!=NULL && strcmp(ret->dances[1].d->music.html_notes,ret->dances[0].d->music.html_notes)==0 )
	ret->dance.music.html_notes = strdup(ret->dances[0].d->music.html_notes);

    if ( ret->dances[0].d->author!=NULL &&
	    ret->dances[1].d->author!=NULL && strcmp(ret->dances[1].d->author,ret->dances[0].d->author)==0  &&
	    ret->dances[0].d->name!=NULL && ret->dances[1].d->name!=NULL ) {
	int n,m, len;
	char *pt;

	pt = strchr(ret->dances[0].d->name,'~');
	if ( pt!=NULL && pt>ret->dances[0].d->name && pt[-1]==' ' )
	    --pt;
	if ( pt==NULL )
	    n = strlen(ret->dances[0].d->name);
	else
	    n = pt-ret->dances[0].d->name;
	pt = strchr(ret->dances[1].d->name,'~');
	if ( pt!=NULL && pt>ret->dances[1].d->name && pt[-1]==' ' )
	    --pt;
	if ( pt==NULL )
	    m = strlen(ret->dances[1].d->name);
	else
	    m = pt-ret->dances[1].d->name;
	if ( m==n && strncmp(ret->dances[0].d->name,ret->dances[1].d->name,n)==0 ) {
	    if ( ret->display_id==NULL )
		ret->display_id = strndup(ret->dances[0].d->name,n);
	    if ( ret->dance.name==NULL && ret->dances[0].d->interpreter!=NULL &&
		    ret->dances[1].d->interpreter!=NULL ) {
		len = n+3+strlen(ret->dances[0].d->interpreter) + 1 + strlen(ret->dances[1].d->interpreter);
		ret->dance.name = malloc(len+1);
		sprintf(ret->dance.name,"%.*s ~ %s/%s", n, ret->dances[0].d->name,
			ret->dances[0].d->interpreter, ret->dances[1].d->interpreter );
	    }
	}
    }

    if ( ret->dance.interpreter==NULL && ret->dances[0].d->interpreter!=NULL &&
	    ret->dances[1].d->interpreter!=NULL ) {
	char *inter;
	inter = ret->dance.interpreter = malloc(strlen( "Comparing (~2000) and (~2000)") + 5 +
		strlen(ret->dances[0].d->interpreter) +
		strlen(ret->dances[1].d->interpreter));
	sprintf(inter,"Comparing %s", ret->dances[0].d->interpreter );
	if ( ret->dances[0].d->year_interpreted>0 )
	    sprintf( inter+strlen(inter), " (%d)", ret->dances[0].d->year_interpreted );
	else if ( ret->dances[0].d->year_interpreted<0 )
	    sprintf( inter+strlen(inter), " (~%d)", -ret->dances[0].d->year_interpreted );
	sprintf( inter+strlen(inter), " %s", ret->dances[1].d->interpreter );
	if ( ret->dances[1].d->year_interpreted>0 )
	    sprintf( inter+strlen(inter), " (%d)", ret->dances[1].d->year_interpreted );
	else if ( ret->dances[1].d->year_interpreted<0 )
	    sprintf( inter+strlen(inter), " (~%d)", -ret->dances[1].d->year_interpreted );
    }
return( (Dance *) ret);
}

Figure *danceLoadFigure(const char *filename, int (*chooseOne)(void *data, char **names,int cnt, int is_dance), void *data) {
    xmlDocPtr doc;
    xmlNodePtr top;
    xmlNodePtr child;
    Figure *ret=NULL;
    int cnt, i;

    setError(NULL);
    doc = xmlParseFile(filename);
    if ( doc==NULL ) {
	setError("Failed to open %s", filename );
return( NULL );
    }
    top = xmlDocGetRootElement(doc);
    if ( xmlStrcmp(top->name,(xmlChar *) "dances")!=0 ) {
	setError("Wrong type of xml file (%s) in %s", top->name, filename );
	xmlFreeDoc(doc);
return( NULL );
    }
    cnt = gxmlCountKids(top,"figure","dance","comp");
    if ( cnt==0 ) {
	setError("File is empty %s (no <dance> or <figure> elements)", filename );
	xmlFreeDoc(doc);
return( NULL );
    } else if ( cnt==1 || chooseOne==NULL ) {
	for ( child=top->children; child!=NULL; child=child->next ) {
	    if ( xmlStrcmp(child->name,(xmlChar *) "figure" )==0 ||
		    xmlStrcmp(child->name,(xmlChar *) "dance" )==0 )
	break;
	}
	ret = danceParseFigureNode(filename,doc,child);
    } else {
	char **ids;
	int is_dance=-2, cur_dance;
	ids = calloc(cnt+1,sizeof(char *));
	child = top->children;
	i=0;
	for ( child=top->children; child!=NULL; child=child->next ) {
	    if ( xmlStrcmp(child->name,(xmlChar *) "figure" )==0 ||
		    xmlStrcmp(child->name,(xmlChar *) "dance" )==0 ||
		    xmlStrcmp(child->name,(xmlChar *) "comp" )==0 ) {
		if ( i>=cnt ) {
		    setError("Internal error: Miscounted figures in file %s:%ld", filename, xmlGetLineNo(child) );
		    xmlFreeDoc(doc);
return( NULL );
		}
		if ( is_dance!=-1 ) {
		    cur_dance = xmlStrcmp(child->name,(xmlChar *) "dance" )==0;
		    if ( is_dance==-2 )
			is_dance = cur_dance;
		    else if ( is_dance!=cur_dance )
			is_dance = -1;
		}
		ids[i] = (char *) xmlGetProp(child,(xmlChar *) "id");
		if ( ids[i]==NULL )
		    setError("Bad format: figure/dance node with no \"id\" attribute in %s", filename, xmlGetLineNo(child) );
		else
		    ++i;
	    }
	}
	if ( i==0 ) {
	    /* Already set error, (no id) */
	    xmlFreeDoc(doc);
	    free(ids);
return( NULL );
	}
	cnt = chooseOne(data,ids,i,is_dance);
	for ( i=0; ids[i]!=NULL; ++i )
	    xmlFree(ids[i]);
	free(ids);
	if ( cnt==-1 ) {
	    /* No need for an error, user's choice */
	    xmlFreeDoc(doc);
return( NULL );
	}
	i=0;
	for ( child=top->children; child!=NULL; child=child->next ) {
	    if ( xmlStrcmp(child->name,(xmlChar *) "figure" )==0 ||
		    xmlStrcmp(child->name,(xmlChar *) "dance" )==0 ||
		    xmlStrcmp(child->name,(xmlChar *) "comp" )==0 ) {
		if ( i==cnt )
	break;
		++i;
	    }
	}
	if ( child==NULL ) {
	    setError("Internal error: node was not there when we went back to look for it %s", filename );
	    xmlFreeDoc(doc);
return( NULL );
	}
	if ( xmlStrcmp(child->name,(xmlChar *) "comp" )==0 )
	    ret = danceParseComparisonNode(filename,doc,child);
	else
	    ret = danceParseFigureNode(filename,doc,child);
    }

    xmlFreeDoc(doc);
return( ret );
}

FigureLib *danceLoadLibrary(const char *filename) {
    xmlDocPtr doc;
    xmlNodePtr top;
    xmlNodePtr child;
    FigureLib *ret = NULL;
    xmlChar *name;
    int i, cnt;

    setError(NULL);
    doc = xmlParseFile(filename);
    if ( doc==NULL ) {
	setError("Failed to open %s", filename );
return( NULL );
    }
    top = xmlDocGetRootElement(doc);
    if ( xmlStrcmp(top->name,(xmlChar *) "dances")!=0 ) {
	setError("Wrong type of xml file (%s) in %s", top->name, filename );
	xmlFreeDoc(doc);
return( NULL );
    }
    cnt = gxmlCountKids(top,"figure","dance","comp");
    if ( cnt==0 ) {
	setError("File is empty %s", filename );
	xmlFreeDoc(doc);
return( NULL );
    }

    ret = calloc(1,sizeof(FigureLib));
    ret->figures  = calloc(cnt+1,sizeof(Figure *));
    ret->style    = ds_unspecified;
    ret->substyle = ss_unspecified;
    name = xmlGetProp(top,(xmlChar *) "style");
    if ( name!=NULL ) {
	ret->style = danceMatchEnum(styleNames,(char *) name);
	xmlFree(name);
    }
    name = xmlGetProp(top,(xmlChar *) "substyle");
    if ( name!=NULL ) {
	ret->substyle = danceMatchEnum(substyleNames,(char *) name);
	xmlFree(name);
    }

    i=0;
    for ( child=top->children; child!=NULL; child=child->next ) {
	if ( xmlStrcmp(child->name,(xmlChar *) "figure" )==0 ||
		xmlStrcmp(child->name,(xmlChar *) "dance" )==0 ||
		xmlStrcmp(child->name,(xmlChar *) "comp" )==0 ) {
	    if ( i>=cnt ) {
		setError("Internal error: Miscounted figures in file %s:%ld (%d>=%d)", filename, xmlGetLineNo(child), i, cnt );
		xmlFreeDoc(doc);
		figureLibFree(ret);
return( NULL );
	    }
	    if ( xmlStrcmp(child->name,(xmlChar *) "comp" )==0 )
		ret->figures[i] = danceParseComparisonNode(filename,doc,child);
	    else
		ret->figures[i] = danceParseFigureNode(filename,doc,child);
	    if ( ret->figures[i]==NULL ) {
		xmlFreeDoc(doc);
		figureLibFree(ret);
return( NULL );
	    }
	    ++i;
	}
    }
    xmlFreeDoc(doc);
return( ret );
}

MusicDescription *danceLoadMusicDescription(const char *filename) {
    xmlDocPtr doc;
    xmlNodePtr top;
    MusicDescription *ret = NULL;

    setError(NULL);
    doc = xmlParseFile(filename);
    if ( doc==NULL ) {
	setError("Failed to open %s", filename );
return( NULL );
    }
    top = xmlDocGetRootElement(doc);
    if ( xmlStrcmp(top->name,(xmlChar *) "music")!=0 ) {
	setError("Wrong type of xml file (%s) in %s", top->name, filename );
	xmlFreeDoc(doc);
return( NULL );
    }

    ret = calloc(1,sizeof(MusicDescription));
    ret->show_minorset = MINORSET_UNSPECIFIED;
    danceParseMusicDescription(filename,ret,top,doc);
    xmlFreeDoc(doc);
return( ret );
}
