/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - eeschema file format support
 *  Copyright (C) 2024..2025 Aron Barath
 *  Copyright (C) 2022..2024 Tibor 'Igor2' Palinkas
 *
 *  (Supported by NLnet NGI0 Entrust Fund in 2024)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA.
 *
 *  Contact:
 *    Project page: http://repo.hu/projects/sch-rnd
 *    contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html
 *    mailing list: http://www.repo.hu/projects/sch-rnd/contact.html
 */

static int eeschema_parse_property__impl(read_ctx_t* const ctx,
	csch_cgrp_t* const libsym, csch_cgrp_t* const symref,
	gsxl_node_t* const src_node, int parent_rot, int force_place);

static int eechema_parse__ignore(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	if(node)
	{
		dbg_printf("node '%s' is ignored (first child: '%s')\n",
			node->parent->str, node->str);
	}

	return 0;
}

static int eechema_parse__ignore_silent(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	return 0;
}

static int eechema_parse__sch_version(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	static const int min_ver = 20211123;
	static const int max_ver = 20250114;
	TODO("Adjust version requirement");

	if(node!=NULL && node->str!=NULL)
	{
		char* end;

		/* verify whether version is properly formatted */
		ctx->ver = strtol(node->str, &end, 10);

		if((*end)!='\0')
		{
			eechema_error(ctx, node, "unexpected layout version syntax (perhaps too new, please file a feature request!)");
			return -1;
		}

		if(ctx->ver<min_ver)
		{
			eechema_error(ctx, node, "wrong version of eeschema schematics: only file version %d or later is supported, yours is %d", min_ver, ctx->ver);
			return -1;
		}

		if(ctx->ver>max_ver)
		{
			rnd_message(RND_MSG_WARNING, "%s:%ld:%ld: eeschema schematics "
				"file is newer than this plugin was designed for, "
				"you may experience glitches, please report them\n",
				ctx->fn, 1+(long)node->line, 1+(long)node->col);
		}

		return attach_attrib(ctx, &ctx->sheet->direct, node->parent,
			node->parent->str, node->str);
	}

	eechema_error(ctx, node, "could not extract eeschema schematics version");
	return -1;
}

static int eechema_parse__sch_attach_attr(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	return attach_attrib(ctx, &ctx->sheet->direct, node->parent,
		node->parent->str, node->str);
}

static int eechema_parse__sch_uuid(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	return attach_attrib(ctx, &ctx->sheet->direct, node->parent,
		"-kicad/uuid", node->str);
}

static int eechema_parse__sch_attach_attr2(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	int res;
	gds_t str;

	gds_init(&str);
	rnd_append_printf(&str, "%s%s", node->parent->str, node->str);

	res = attach_attrib(ctx, &ctx->sheet->direct, node->parent, str.array,
		node->next->str);

	gds_uninit(&str);

	return res;
}

static int eechema_parse__sch_titleblock(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	return eechema_parse_titleblock(ctx, dst, node);
}

static int eechema_parse__sch_paper(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	float fw, fh;

	if(node==NULL || node->str==NULL)
	{
	could_not_extract:;
		eechema_error(ctx, node, "could not extract eeschema paper size");
		return -1;
	}

	if(attach_attrib(ctx, &ctx->sheet->direct, node->parent,
		node->parent->str, node->str)!=0)
	{
		return -1;
	}

	if(strcmp(node->str, "User")==0)
	{
		if(node->next==NULL || node->next->next==NULL ||
			node->next->next->next!=NULL)
		{
			goto could_not_extract;
		}

		fw = strtod(node->next->str, NULL);
		fh = strtod(node->next->next->str, NULL);
	}
	else
	{
		const struct paper_dim* const dim = paper_dim_lookup(node->str);

		if(NULL==dim)
		{
			eechema_error(ctx, node, "unknown paper dimension string: '%s', "
				"please report it!", node->str);
			return -1;
		}

		fw = dim->w;
		fh = dim->h;

		if(node->next!=NULL)
		{
			if(strcmp(node->next->str, "portrait")==0)
			{
				float t = fw;
				fw = fh;
				fh = t;
			}
			else
			{
				eechema_error(ctx, node, "unexpected token after page size: "
					"'%s', please report it!", node->next->str);
				return -1;
			}
		}
	}

	return eeschema_process_wks(ctx, fw, fh);
}

static cache_sym_t* eeschema_dup_libsym(read_ctx_t* const ctx,
	gsxl_node_t* const node, cache_sym_t* const libsym, const char* const unit)
{
	char* const name =
		eeschema_make_symname(ctx, node, libsym->name->str, unit);

	cache_sym_t* cs;

	if(!name)
	{
		return NULL;
	}

	cs = (cache_sym_t*)htsp_get(&ctx->syms, name);

	/* merge libsym data together */
	if(cs)
	{
		return cs;
	}

	cs = (cache_sym_t*)calloc(1, sizeof(cache_sym_t));

	if(!cs)
	{
		eechema_error(ctx, node,
			"could not allocate memory for library symbol: '%s'", name);
		free(name);
		return NULL;
	}

	memcpy(cs, libsym, sizeof(*cs));

	cs->sym_name = name;

	cs->grp = csch_cgrp_dup(ctx->sheet, ctx->loclib, libsym->grp, 0);

	{
		csch_source_arg_t* src = make_src_attrib(ctx, node);

		csch_attrib_set(&cs->grp->attr, CSCH_ATP_USER_DEFAULT, "name",
			cs->sym_name, src, NULL);
	}

	dbg_printf("add symbol to cache: '%s' (dupe)\n", cs->sym_name);
	htsp_insert(&ctx->syms, (char*)cs->sym_name, cs);

	return cs;
}

static int eechema_load_cache_sym(read_ctx_t* const ctx, gsxl_node_t* data)
{
	csch_sheet_t* const sheet = ctx->alien.sheet;

	cache_sym_t* const cs = (cache_sym_t*)calloc(1, sizeof(cache_sym_t));

	unsigned old_flip_y;
	double old_oy;

	if(!cs)
	{
		eechema_error(ctx, data, "could not allocate cache sym");
		return -1;
	}

	/* this is not designed well... */
	cs->sym_name = eeschema_make_symname(ctx, data, data->str, "0");
	cs->sym_data = data->next;
	cs->name     = data;

	/* pin numbers are visible by default */
	cs->show_pin_numbers = 1;

	/* pin names are visible by default */
	cs->show_pin_names = 1;

	cs->pin_names_offset = 0.508; /* 20 mil */

	if(htsp_get(&ctx->syms, cs->sym_name)!=NULL)
	{
		eechema_error(ctx, data, "duplicate library symbol");
		free(cs);
		return -1;
	}

	htsp_insert(&ctx->syms, (char*)cs->sym_name, cs);

	/* make sure symbol loclib is available - need to create all symdefs there */
	if(ctx->loclib==NULL)
	{
		int alloced;
		csch_source_arg_t* src;

		src = csch_attrib_src_c(ctx->fn, 0, 0, NULL);
		ctx->loclib = csch_loclib_get_root_by_name(sheet, "symbol", src, 1,
			&alloced);

		if(ctx->loclib==NULL)
		{
			eechema_error(ctx, data, "failed to allocate symbol local lib (root)");
			return -1;
		}
	}

	{
		csch_source_arg_t* src;

		cs->grp = csch_cgrp_alloc(sheet, ctx->loclib, csch_oid_new(sheet,
			ctx->loclib));

		if(cs->grp==NULL)
		{
			eechema_error(ctx, data, "failed to allocate symbol in local lib");
			return -1;
		}

		src = make_src_attrib(ctx, data->parent);

		csch_attrib_set(&cs->grp->attr, CSCH_ATP_USER_DEFAULT, "role",
			"symbol", src, NULL);

		csch_attr_side_effects(cs->grp, "role");

		src = make_src_attrib(ctx, data);

		csch_attrib_set(&cs->grp->attr, CSCH_ATP_USER_DEFAULT, "name",
			cs->sym_name, src, NULL);
	}

	ctx->cur_libsym = cs;
	ctx->cur_libsym0 = cs;
	old_flip_y = ctx->alien.flip_y;
	old_oy = ctx->alien.oy;
	ctx->alien.flip_y = 0;
	ctx->alien.oy = 0;

	dbg_printf("add symbol to cache: '%s'\n", cs->sym_name);

	if(eechema_parse_libsym(ctx, cs->grp, cs->sym_data)!=0)
	{
		ctx->cur_libsym = NULL;

		eechema_error(ctx, data, "could not parse symbol data");
		return -1;
	}

	if(ctx->cur_libsym0==ctx->cur_libsym)
	{
		eeschema_dup_libsym(ctx, cs->sym_data, ctx->cur_libsym0, "1");
	}

	ctx->cur_libsym = NULL;
	ctx->cur_libsym0 = NULL;
	ctx->alien.flip_y = old_flip_y;
	ctx->alien.oy = old_oy;

	return 0;
}

static int eechema_parse__sch_lib_symbols(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	for(;node;node=node->next)
	{
		if(strcmp(node->str, "symbol")==0)
		{
			gsxl_node_t* data = node->children;

			if(eechema_load_cache_sym(ctx, data)!=0)
			{
				return -1;
			}
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	return 0;
}

static void eeschema_hide_unhandled_floaters(read_ctx_t* const ctx,
	csch_cgrp_t* const libsym, csch_cgrp_t* const symref)
{
	htip_entry_t* ent;

	dbg_printf("hiding unhandled floaters\n");

	for(ent=htip_first(&libsym->id2obj);ent;ent=htip_next(&libsym->id2obj, ent))
	{
		csch_text_t* const text = (csch_text_t*)ent->value;

		if(text->hdr.type==CSCH_CTYPE_TEXT)
		{
			if(text->dyntext && !TEXT_SYM_ATTR_HANDLED(text))
			{
				TEXT_SYM_ATTR_HANDLED(text) = 1;
				dbg_printf("  hide floater: '%s'\n", text->text);
				eeschema_sym_child_hide(ctx, symref, (csch_chdr_t*)text);
			}
		}
	}

	dbg_printf("done\n");
}

static void eeschema_hide_new_floater(read_ctx_t* const ctx,
	csch_sheet_t* const sheet, csch_cgrp_t* const libsym,
	csch_text_t* const floater, csch_cgrp_t* const excluded_grpref)
{
	htip_entry_t* ent;

	dbg_printf("hiding new floater in existing grprefs\n");

	for(ent=htip_first(&sheet->direct.id2obj);
		ent;
		ent=htip_next(&sheet->direct.id2obj, ent))
	{
		csch_cgrp_t* const grpref = (csch_cgrp_t*)ent->value;

		if(grpref->hdr.type==CSCH_CTYPE_GRP_REF &&
			grpref->data.ref.grp==libsym && grpref!=excluded_grpref)
		{
			dbg_printf("  hide floater in oid=%ld\n", (long)grpref->hdr.oid);
			eeschema_sym_child_hide(ctx, grpref, (csch_chdr_t*)floater);
		}
	}

	dbg_printf("done\n");
}

static int eechema_parse__sch_symbol(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* lib_id = NULL;
	gsxl_node_t* lib_name = NULL;
	gsxl_node_t* at = NULL;
	gsxl_node_t* unit = NULL;
	gsxl_node_t* uuid = NULL;

	gsxl_node_t* const node0  = node;
	gsxl_node_t* const parent = node->parent;

	float x, y;
	int rot;
	csch_cgrp_t* sym;
	cache_sym_t* cs;
	int res;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "lib_id")==0)
		{
			lib_id = node->children;
		}
		else
		if(strcmp(node->str, "lib_name")==0)
		{
			lib_name = node->children;
		}
		else
		if(strcmp(node->str, "unit")==0)
		{
			unit = node->children;
		}
		else
		if(strcmp(node->str, "at")==0)
		{
			at = node->children;
		}
		else
		if(strcmp(node->str, "uuid")==0)
		{
			uuid = node->children;
		}
	}

	if(!lib_id || !unit || !at)
	{
		eechema_error(ctx, parent, "missing info from 'symbol' object");
		return -1;
	}

	if(eechema_parse_at(ctx, at, &x, &y, &rot)!=0)
	{
		return -1;
	}

	{
		char* name;

		if(lib_name==NULL)
		{
			name = eeschema_make_symname(ctx, parent, lib_id->str, unit->str);
		}
		else
		{
			name = eeschema_make_symname2(ctx, parent, lib_name->str,
				unit->str);
		}

		if(!name) { return -1; }

		dbg_printf("  sym lookup: \"%s\"\n", name);

		cs = htsp_get(&ctx->syms, name);

		free(name);
	}

	if(cs==NULL)
	{
		eechema_error(ctx, parent, "could not find symbol def: '%s'",
			lib_id->str);
		return -1;
	}

	sym = csch_cgrp_ref_alloc(ctx->alien.sheet, &ctx->alien.sheet->direct,
		csch_oid_new(ctx->alien.sheet, &ctx->alien.sheet->direct));

	if(sym==NULL)
	{
		eechema_error(ctx, parent, "failed to create blank symbol");
		return -1;
	}

	csch_cgrp_update(ctx->alien.sheet, cs->grp, 1);

	sym->data.ref.grp = cs->grp;

	dbg_printf("place '%s' to (%f;%f) with rotation %i\n", cs->sym_name,
		x, y, rot);

	ctx->cur_sym_x = csch_alien_coord_x(&ctx->alien, x);
	ctx->cur_sym_y = csch_alien_coord_y(&ctx->alien, y);

	ctx->cur_sym_rot              = rot;
	ctx->cur_sym_mirx             = 0;
	ctx->cur_sym_miry             = 0;
	ctx->cur_sym_exclude_from_sim = 0;
	ctx->cur_sym_in_bom           = 0;
	ctx->cur_sym_on_board         = 0;

	ctx->cur_sym = sym;
	{
		eeschema_reset_floater_handled_flag(ctx, cs->grp);

		res = eechema_parse_schsymdata(ctx, dst, node0);

		eeschema_hide_unhandled_floaters(ctx, cs->grp, sym);
	}
	ctx->cur_sym = NULL;

	sym->x += ctx->cur_sym_x;
	sym->y += ctx->cur_sym_y;

	sym->spec_rot = rot;

	sym->mirx = ctx->cur_sym_mirx;
	sym->miry = ctx->cur_sym_miry;

	if(res==0 && !ctx->cur_sym_exclude_from_sim && cs->exclude_from_sim)
	{
		res = attach_attrib(ctx, sym, cs->exclude_from_sim_node, a_sim_omit,
			"yes");
	}

	if(res==0 && !ctx->cur_sym_in_bom)
	{
		res = attach_attrib(ctx, sym, cs->in_bom_node, a_in_bom,
			cs->in_bom_node->str);
	}

	if(res==0 && !ctx->cur_sym_on_board && !cs->on_board)
	{
		res = attach_attrib(ctx, sym, cs->on_board_node, a_pcb_omit, "yes");
	}

	if(res==0 && uuid!=NULL)
	{
		res = attach_attrib(ctx, sym, uuid, "-kicad/uuid", uuid->str);
	}

	/* post-proc */
	if(res==0)
	{
		csch_cgrp_ref_render(ctx->alien.sheet, sym);
		csch_cgrp_ref_update(ctx->alien.sheet, sym, 1);
	}

	return res;
}

static int eechema_parse__sch_wire(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* pts = NULL;

	gsxl_node_t* const parent = node->parent;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "pts")==0)
		{
			pts = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!pts)
	{
		eechema_error(ctx, parent, "missing 'pts' data from 'wire' object");
		return -1;
	}

	return eeschema_render_wires(ctx, &ctx->sheet->direct, parent, pts);
}

static int eechema_parse__sch_bus(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* pts = NULL;

	gsxl_node_t* const parent = node->parent;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "pts")==0)
		{
			pts = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!pts)
	{
		eechema_error(ctx, parent, "missing 'pts' data from 'bus' object");
		return -1;
	}

	if(!eeschema_render_polyline(ctx, &ctx->sheet->direct, parent, pts, "bus",
		NULL, 0, 0, 0, 0))
	{
		return -1;
	}

	return 0;
}

static int eechema_parse_busentry(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* at = NULL;
	gsxl_node_t* size = NULL;

	gsxl_node_t* const parent = node->parent;

	float x, y;
	float sx, sy;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "at")==0)
		{
			at = node->children;
		}
		else
		if(strcmp(node->str, "size")==0)
		{
			size = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!at)
	{
		eechema_error(ctx, parent, "missing 'at' data from 'bus_entry' object");
		return -1;
	}

	if(!size)
	{
		eechema_error(ctx, parent, "missing 'size' data from 'bus_entry' object");
		return -1;
	}

	if(eechema_parse_xy(ctx, at, &x, &y)!=0)
	{
		return -1;
	}

	if(eechema_parse_xy(ctx, size, &sx, &sy)!=0)
	{
		return -1;
	}

	return eeschema_render_busentry_decor(ctx, dst, parent, x, y, sx, sy,
		"busterm-primary");
}

static int eechema_parse_polyline(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* pts = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);
	const char* fill = NULL;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "pts")==0)
		{
			pts = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		if(strcmp(node->str, "fill")==0)
		{
			int res = eeschema_has_fill(ctx, node->children);

			if(res<0) { return res; }

			if(res)
			{
				fill = eeschema_get_fill(ctx);
			}
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!pts)
	{
		eechema_error(ctx, parent, "missing 'pts' data from 'polyline' object");
		return -1;
	}

	if(!eeschema_render_polyline(ctx, dst, parent, pts, stroke, fill,
		0, 0, 0, 0))
	{
		return -1;
	}

	return 0;
}

static int eechema_parse_rectangle(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* start = NULL;
	gsxl_node_t* end = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);
	const char* fill = NULL;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "start")==0)
		{
			start = node->children;
		}
		else
		if(strcmp(node->str, "end")==0)
		{
			end = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		if(strcmp(node->str, "fill")==0)
		{
			int res = eeschema_has_fill(ctx, node->children);

			if(res<0) { return res; }

			if(res)
			{
				fill = eeschema_get_fill(ctx);
			}
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!start || !end)
	{
		eechema_error(ctx, parent, "missing info data from 'rectangle' object");
		return -1;
	}

	if(!eeschema_render_rectangle(ctx, dst, parent, start, end, stroke, fill))
	{
		return -1;
	}

	return 0;
}

static int eechema_parse_circle(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* center = NULL;
	gsxl_node_t* radius = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);
	const char* fill = NULL;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "center")==0)
		{
			center = node->children;
		}
		else
		if(strcmp(node->str, "radius")==0)
		{
			radius = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		if(strcmp(node->str, "fill")==0)
		{
			int res = eeschema_has_fill(ctx, node->children);

			if(res<0) { return res; }

			if(res)
			{
				fill = eeschema_get_fill(ctx);
			}
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!center || !radius)
	{
		eechema_error(ctx, parent, "missing info data from 'circle' object");
		return -1;
	}

	if(!eeschema_render_circle(ctx, dst, parent, center, radius, stroke, fill))
	{
		return -1;
	}

	return 0;
}

static int eechema_parse_arc(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* start = NULL;
	gsxl_node_t* mid   = NULL;
	gsxl_node_t* end   = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);
	const char* fill = NULL;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "start")==0)
		{
			start = node->children;
		}
		else
		if(strcmp(node->str, "mid")==0)
		{
			mid = node->children;
		}
		else
		if(strcmp(node->str, "end")==0)
		{
			end = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		if(strcmp(node->str, "fill")==0)
		{
			int res = eeschema_has_fill(ctx, node->children);

			if(res<0) { return res; }

			if(res)
			{
				fill = eeschema_get_fill(ctx);
			}
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!start || !mid || !end)
	{
		eechema_error(ctx, parent, "missing info data from 'arc' object");
		return -1;
	}

	if(!eeschema_render_arc(ctx, dst, parent, start, mid, end, stroke, fill))
	{
		return -1;
	}

	return 0;
}

static int eechema_parse_bezier(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	gsxl_node_t* pts = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);
	const char* fill = NULL;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "pts")==0)
		{
			pts = node->children;
		}
		else
		if(strcmp(node->str, "stroke")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		if(strcmp(node->str, "fill")==0)
		{
			int res = eeschema_has_fill(ctx, node->children);

			if(res<0) { return res; }

			if(res)
			{
				fill = eeschema_get_fill(ctx);
			}
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!pts)
	{
		eechema_error(ctx, parent, "missing 'pts' data from 'bezier' object");
		return -1;
	}

	return eeschema_render_bezier(ctx, dst, parent, pts, stroke, fill);
}

static int eechema_parse_image(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	float x, y;
	gsxl_node_t* at = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "at")==0)
		{
			at = node->children;
		}
		else
		if(strcmp(node->str, "uuid")==0 ||
			strcmp(node->str, "scale")==0 ||
			strcmp(node->str, "data")==0)
		{
			/* ignore these */
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!at)
	{
		eechema_error(ctx, parent, "missing 'at' data from 'image' object");
		return -1;
	}

	if(eechema_parse_xy(ctx, at, &x, &y)!=0)
	{
		return -1;
	}

	if(!eeschema_render_image_placeholder(ctx, dst, parent, x, y, stroke))
	{
		return -1;
	}

	return 0;
}

static int eechema_parse_noconnect(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	float x, y;
	gsxl_node_t* at = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "at")==0)
		{
			at = node->children;
		}
		else
		if(strcmp(node->str, "uuid")==0)
		{
			/* ignore this */
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	if(!at)
	{
		eechema_error(ctx, parent, "missing 'at' data from 'no_connect' object");
		return -1;
	}

	if(eechema_parse_xy(ctx, at, &x, &y)!=0)
	{
		return -1;
	}

	if(!eeschema_render_noconnect(ctx, dst, parent, x, y, stroke))
	{
		return -1;
	}

	return 0;
}

static csch_text_t* eechema_parse_text__impl(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* node, const char* const objtype,
	const char* const text_str, const char* const stroke,
	const char* ignore_list[])
{
	gsxl_node_t* at      = NULL;
	gsxl_node_t* justify = NULL;

	gsxl_node_t* const parent = node->parent;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "at")==0)
		{
			at = node->children;
		}
		else
		if(strcmp(node->str, "effects")==0)
		{
			gsxl_node_t* child = node->children;

			for(;child;child=child->next)
			{
				if(strcmp(child->str, "justify")==0)
				{
					justify = child->children;
				}
				else
				if(strcmp(child->str, "font")==0 ||
					strcmp(child->str, "hide")==0)
				{
					/* ignore these */
				}
				else
				{
					unexpected_child(ctx, node, child);
					return NULL;
				}
			}
		}
		else
		if(strcmp(node->str, "exclude_from_sim")==0 ||
			strcmp(node->str, "uuid")==0)
		{
			/* ignore these */
		}
		else
		{
			const char** ignore = ignore_list;

			while((*ignore)!=NULL)
			{
				if(strcmp((*ignore), node->str)==0)
				{
					break;
				}

				++ignore;
			}

			if((*ignore)==NULL)
			{
				unexpected_child(ctx, node->parent, node);
				return NULL;
			}
		}
	}

	if(!at)
	{
		eechema_error(ctx, parent, "missing data from '%s' object", objtype);
		return NULL;
	}

	return (csch_text_t*)eeschema_render_text(ctx, dst, parent, at, justify,
		text_str, stroke);
}

static int eechema_parse_text(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	static const char* text_ignore_list[] = { NULL };

	if(!eechema_parse_text__impl(ctx, dst, node->next, "text", node->str,
		eeschema_get_stroke(ctx), text_ignore_list))
	{
		return -1;
	}

	return 0;
}

static int eechema_parse__sch_label(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	const char* str;
	const char* stroke;
	float x, y;
	int rot;

	csch_text_t*     text;
	csch_line_t*     wire;
	csch_rtree_it_t  it;
	csch_rtree_box_t bbox;

	static const char* label_ignore_list[] =
	{
		"fields_autoplaced",
		NULL
	};

	/* fetch (at) quick, we need it for lookup */
	{
		gsxl_node_t* at = node;

		for(;at;at=at->next)
		{
			if(strcmp(at->str, "at")==0)
			{
				break;
			}
		}

		if(!at)
		{
			eechema_error(ctx, node->parent,
				"missing 'at' node from 'label' object");
			return -1;
		}

		if(eechema_parse_at(ctx, at->children, &x, &y, &rot)!=0)
		{
			return -1;
		}
	}

	bbox.x1 = csch_alien_coord_x(&ctx->alien, x) - 1;
	bbox.y1 = csch_alien_coord_y(&ctx->alien, y) - 1;
	bbox.x2 = bbox.x1 + 2;
	bbox.y2 = bbox.y1 + 2;

	for(wire=csch_rtree_first(&it, &ctx->sheet->dsply[CSCH_DSPLY_WIRE], &bbox);
		wire;wire=csch_rtree_next(&it))
	{
		if(wire->hdr.type==CSCH_CTYPE_LINE &&
			wire->hdr.parent->role==CSCH_ROLE_WIRE_NET)
		{
			break;
		}
	}

	if(wire)
	{
		stroke = "wire";
		str    = "%../A.name%";

		dst = wire->hdr.parent;

		if(attach_attrib(ctx, dst, node->parent, "name", node->str)!=0)
		{
			return -1;
		}
	}
	else
	{
		stroke = "sheet-decor";
		str    = node->str;
	}

	text = eechema_parse_text__impl(ctx, dst, node->next, "label", str,
		stroke, label_ignore_list);

	if(!text)
	{
		return -1;
	}

	if(wire)
	{
		text->dyntext = 1;
		text->hdr.floater = 1;

		csch_text_dyntext_render(text);
		csch_text_update(ctx->sheet, text, 1);
	}

	return 0;
}

static int eechema_parse_X_label(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const node,
	const char* const obj_name, int (*render_decor)(read_ctx_t* const ctx,
		csch_cgrp_t* const dst, gsxl_node_t* const objroot,
		csch_text_t* const text, const char* const shape, const float xf,
		const float yf, const int rot, const char* const stroke))
{
	float x, y;
	int rot;

	csch_cgrp_t* label;
	csch_text_t* text;
	gsxl_node_t* shape = NULL;

	const char* const stroke = "sym-decor";

	static const char* glob_ignore_list[] =
	{
		"fields_autoplaced",
		"property",
		"shape",
		NULL
	};

	/* fetch (at) and (shape) for further processing */
	{
		gsxl_node_t* n = node;
		gsxl_node_t* at = NULL;

		for(;n && (!shape || !at);n=n->next)
		{
			if(strcmp(n->str, "at")==0)
			{
				at = n;
			}
			else
			if(strcmp(n->str, "shape")==0)
			{
				shape = n;
			}
		}

		if(!at)
		{
			eechema_error(ctx, node->parent,
				"missing 'at' node from '%s' object", obj_name);
			return -1;
		}

		if(!shape || !shape->children)
		{
			eechema_error(ctx, node->parent,
				"missing/bad 'shape' node from '%s' object", obj_name);
			return -1;
		}

		if(eechema_parse_at(ctx, at->children, &x, &y, &rot)!=0)
		{
			return -1;
		}
	}

	label = csch_cgrp_alloc(ctx->sheet, &ctx->sheet->direct,
		csch_oid_new(ctx->sheet, &ctx->sheet->direct));

	{
		csch_source_arg_t* src;

		src = make_src_attrib(ctx, node->parent);
		csch_attrib_set(&label->attr, CSCH_ATP_USER_DEFAULT, "role",
			"symbol", src, NULL);
		csch_attr_side_effects(label, "role");

		src = make_src_attrib(ctx, node);
		csch_attrib_set(&label->attr, CSCH_ATP_USER_DEFAULT, "name",
			node->str, src, NULL);
	}

	text = eechema_parse_text__impl(ctx, label, node->next, obj_name,
		node->str, stroke, glob_ignore_list);

	if(!text)
	{
		return -1;
	}

	if(0!=render_decor(ctx, label, node, text, shape->children->str, x, y,
		rot, stroke))
	{
		return -1;
	}

	if(eeschema_add_forge(ctx, node, label, "name"))
	{
		return -1;
	}

	text->dyntext = 1;
	free(text->text);
	text->text = rnd_strdup("%../A.name%");
	csch_text_dyntext_render(text);

	{
		csch_source_arg_t* const src = make_src_attrib(ctx, node);

		if(!csch_alien_mkpin_line(&ctx->alien, src, label, x, y, x, y))
		{
			eechema_error(ctx, node->parent, "could not create %s pin line",
				obj_name);
			return -1;
		}
	}

	/* process (property) nodes */
	{
		gsxl_node_t* n = node;

		for(;n;n=n->next)
		{
			if(strcmp(n->str, "property")==0)
			{
				if(eeschema_parse_property__impl(ctx, label, NULL,
					n->children, rot, 1)!=0)
				{
					return -1;
				}
			}
		}
	}

	return 0;
}

static int eechema_parse_globallabel(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	return eechema_parse_X_label(ctx, dst, node, "global_label",
		eeschema_render_global_decor);
}

static int eechema_parse_hierarchicallabel(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	return eechema_parse_X_label(ctx, dst, node, "hierarchical_label",
		eeschema_render_hierarchical_decor);
}

static int eechema_parse_netclassflag(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	float x, y;
	int rot;
	float length;

	csch_cgrp_t* grp;
	gsxl_node_t* shape = NULL;

	const char* const stroke = "sym-decor";

	/* fetch (at), (length) and (shape) for further processing */
	{
		gsxl_node_t* n = node;
		gsxl_node_t* at = NULL;
		gsxl_node_t* len = NULL;

		for(;n && (!shape || !at || !len);n=n->next)
		{
			if(strcmp(n->str, "at")==0)
			{
				at = n;
			}
			else
			if(strcmp(n->str, "length")==0)
			{
				len = n;
			}
			else
			if(strcmp(n->str, "shape")==0)
			{
				shape = n;
			}
		}

		if(!at)
		{
			eechema_error(ctx, node->parent,
				"missing 'at' node from 'netclass_flag' object");
			return -1;
		}

		if(!len)
		{
			eechema_error(ctx, node->parent,
				"missing 'length' node from 'netclass_flag' object");
			return -1;
		}

		if(!shape || !shape->children)
		{
			eechema_error(ctx, node->parent,
				"missing/bad 'shape' node from 'netclass_flag' object");
			return -1;
		}

		if(eechema_parse_at(ctx, at->children, &x, &y, &rot)!=0)
		{
			return -1;
		}

		if(eechema_parse_num(ctx, len->children, &length)!=0)
		{
			return -1;
		}
	}

	grp = csch_cgrp_alloc(ctx->sheet, &ctx->sheet->direct,
		csch_oid_new(ctx->sheet, &ctx->sheet->direct));

	{
		csch_source_arg_t* src;

		src = make_src_attrib(ctx, node->parent);
		csch_attrib_set(&grp->attr, CSCH_ATP_USER_DEFAULT, "role",
			"symbol", src, NULL);
		csch_attr_side_effects(grp, "role");

		src = make_src_attrib(ctx, node);
		csch_attrib_set(&grp->attr, CSCH_ATP_USER_DEFAULT, "name",
			node->str, src, NULL);
	}

	if(0!=eeschema_render_netclass_decor(ctx, grp, node, shape->children->str,
		length, x, y, rot, stroke))
	{
		return -1;
	}

	/* process (property) nodes */
	{
		gsxl_node_t* n = node;

		for(;n;n=n->next)
		{
			if(strcmp(n->str, "property")==0)
			{
				if(eeschema_parse_property__impl(ctx, grp, NULL,
					n->children, rot, 1)!=0)
				{
					return -1;
				}
			}
		}
	}

	return 0;
}

static int eechema_parse__sht_pin(read_ctx_t* const ctx,
	csch_cgrp_t* const dst, gsxl_node_t* const node)
{
	float x, y;
	int rot;

	csch_cgrp_t* grp;
	csch_text_t* text;

	const char* shape = node->next->str;

	const char* const stroke = "term-decor";

	static const char* shtpin_ignore_list[] = { NULL };

	/* fetch (at) for further processing */
	{
		gsxl_node_t* n = node->next->next;
		gsxl_node_t* at = NULL;

		for(;n && !at;n=n->next)
		{
			if(strcmp(n->str, "at")==0)
			{
				at = n;
			}
		}

		if(!at)
		{
			eechema_error(ctx, node->parent,
				"missing 'at' node from 'pin' object");
			return -1;
		}

		if(eechema_parse_at(ctx, at->children, &x, &y, &rot)!=0)
		{
			return -1;
		}
	}

	grp = csch_cgrp_alloc(ctx->sheet, &ctx->sheet->direct,
		csch_oid_new(ctx->sheet, &ctx->sheet->direct));

	{
		csch_source_arg_t* src;

		src = make_src_attrib(ctx, node->parent);
		csch_attrib_set(&grp->attr, CSCH_ATP_USER_DEFAULT, "role",
			"symbol", src, NULL);
		csch_attr_side_effects(grp, "role");

		src = make_src_attrib(ctx, node);
		csch_attrib_set(&grp->attr, CSCH_ATP_USER_DEFAULT, "name",
			node->str, src, NULL);
	}

	text = eechema_parse_text__impl(ctx, grp, node->next->next, "pin",
		node->str, stroke, shtpin_ignore_list);

	if(!text)
	{
		return -1;
	}

	/* swap input/output shapes */
	if(strcmp(shape, "input")==0)
	{
		shape = "output";
	}
	else
	if(strcmp(shape, "output")==0)
	{
		shape = "input";
	}

	/* rotation: ouch */
	if(0!=eeschema_render_hierarchical_decor(ctx, grp, node, text, shape,
		x, y, (rot+180)%360, stroke))
	{
		return -1;
	}

	text->dyntext = 1;
	free(text->text);
	text->text = rnd_strdup("%../A.name%");
	csch_text_dyntext_render(text);

	{
		csch_source_arg_t* const src = make_src_attrib(ctx, node);

		if(!csch_alien_mkpin_line(&ctx->alien, src, grp, x, y, x, y))
		{
			eechema_error(ctx, node->parent, "could not create pin line");
			return -1;
		}
	}

	return 0;
}

static int eechema_parse__sch_sheet(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	float x, y;
	float w, h;

	csch_cgrp_t* grp;

	const char* const stroke = "sym-decor";

	/* fetch (at) and (size) for further processing */
	{
		gsxl_node_t* n = node;
		gsxl_node_t* at = NULL;
		gsxl_node_t* size = NULL;

		for(;n && (!at || !size);n=n->next)
		{
			if(strcmp(n->str, "at")==0)
			{
				at = n;
			}
			else
			if(strcmp(n->str, "size")==0)
			{
				size = n;
			}
		}

		if(!at)
		{
			eechema_error(ctx, node->parent,
				"missing 'at' node from 'sheet' object");
			return -1;
		}

		if(!size)
		{
			eechema_error(ctx, node->parent,
				"missing 'size' node from 'sheet' object");
			return -1;
		}

		if(eechema_parse_xy(ctx, at->children, &x, &y)!=0)
		{
			return -1;
		}

		if(eechema_parse_xy(ctx, size->children, &w, &h)!=0)
		{
			return -1;
		}
	}

	grp = csch_cgrp_alloc(ctx->sheet, &ctx->sheet->direct,
		csch_oid_new(ctx->sheet, &ctx->sheet->direct));

	{
		csch_source_arg_t* src;

		src = make_src_attrib(ctx, node->parent);
		csch_attrib_set(&grp->attr, CSCH_ATP_USER_DEFAULT, "role",
			"symbol", src, NULL);
		csch_attr_side_effects(grp, "role");
	}

	if(!csch_alien_mkrect(&ctx->alien, grp, x, y, x+w, y+h, stroke, NULL))
	{
		eechema_error(ctx, node->parent,
			"could not create 'sheet' rect decor");
		return -1;
	}

	/* process (property) and (pin) nodes */
	{
		gsxl_node_t* n = node;

		for(;n;n=n->next)
		{
			if(strcmp(n->str, "property")==0)
			{
				if(eeschema_parse_property__impl(ctx, grp, NULL,
					n->children, 0, 1)!=0)
				{
					return -1;
				}
			}
			else
			if(strcmp(n->str, "pin")==0)
			{
				if(eechema_parse__sht_pin(ctx, grp, n->children)!=0)
				{
					return -1;
				}
			}
		}
	}

	return 0;
}

static int eechema_parse__libsym_pin_numname(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node, float* offset, int* hide)
{
	*hide = 0;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "hide")==0)
		{
			*hide = 1;
		}
		else
		if(strcmp(node->str, "offset")==0)
		{
			char* end;

			if(node->children->next!=NULL)
			{
				unexpected_child(ctx, node->parent, node);
				return -1;
			}

			*offset = strtod(node->children->str, &end);

			if(!end || (*end)!=0)
			{
				eechema_error(ctx, node,
					"value is not a valid float number: '%s'", node->str);
				return -1;
			}
		}
		else
		{
			unexpected_child(ctx, node->parent, node);
			return -1;
		}
	}

	return 0;
}

static int eechema_parse__libsym_pin_numbers(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int hide;

	int res = eechema_parse__libsym_pin_numname(ctx, dst, node,
		&ctx->cur_libsym->pin_numbers_offset, &hide);

	ctx->cur_libsym->show_pin_numbers = !hide;

	return res;
}

static int eechema_parse__libsym_pin_names(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int hide;

	int res = eechema_parse__libsym_pin_numname(ctx, dst, node,
		&ctx->cur_libsym->pin_names_offset, &hide);

	ctx->cur_libsym->show_pin_names = !hide;

	return res;
}

static int eechema_parse__libsym_exclude_from_sim(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int res = eechema_parse_yesno(ctx, node);

	if(res<0)
	{
		return res;
	}

	ctx->cur_libsym->exclude_from_sim = res;
	ctx->cur_libsym->exclude_from_sim_node = node;

	return 0;
}

static int eechema_parse__libsym_in_bom(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int res = eechema_parse_yesno(ctx, node);

	if(res<0)
	{
		return res;
	}

	ctx->cur_libsym->in_bom = res;
	ctx->cur_libsym->in_bom_node = node;

	return 0;
}

static int eechema_parse__libsym_on_board(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int res = eechema_parse_yesno(ctx, node);

	if(res<0)
	{
		return res;
	}

	ctx->cur_libsym->on_board = res;
	ctx->cur_libsym->on_board_node = node;

	return 0;
}

static int eechema_parse__libsym_symbol(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	const char* const name = node->str;

	const char* str;
	const char* end;

	char* unit;

	int res;

	/* let's locate the unit number in the string */
	/* names look like this: "R_0_1" */

	str = strrchr(name, '_');
	/*              "R_0_1" */
	/* we are here:     ^   */

	if(!str)
	{
	inval_subsym_name:;
		eechema_error(ctx, node, "invalid sub-symbol name");
		return -1;
	}

	end = str;

	do
	{
		--str;
	}
	while(name<str && (*str)!='_');
	/*              "R_0_1" */
	/* we are here:   ^     */

	if(name==str)
	{
		goto inval_subsym_name;
	}

	++str;

	unit = (char*)malloc(end-str+1);

	if(!unit)
	{
		eechema_error(ctx, node, "could not allocate tmp buffer for 'unit'");
		return -1;
	}

	memcpy(unit, str, end-str);
	unit[end-str] = 0;

	if(strcmp(unit, "0")!=0)
	{
		ctx->cur_libsym = eeschema_dup_libsym(ctx, node, ctx->cur_libsym0,
			unit);

		if(!ctx->cur_libsym)
		{
			free(unit);
			return -1;
		}

		dbg_printf("libsym '%s' duped to '%s'\n", ctx->cur_libsym0->sym_name,
			ctx->cur_libsym->sym_name);

		dst = ctx->cur_libsym->grp;
	}

	res = eechema_parse_libsymdata(ctx, dst, node);

	free(unit);

	return res;
}

static int eeschema_parse_property__impl(read_ctx_t* const ctx,
	csch_cgrp_t* const libsym, csch_cgrp_t* const symref,
	gsxl_node_t* const src_node, int parent_rot, int force_place)
{
	const char* key;
	const char* value;
	const char* stroke;

	gsxl_node_t* node;
	gsxl_node_t* at;
	gsxl_node_t* justify;
	csch_text_t* t;

	char* str;

	int hidden    = 0;
	int show_name = 0;

	if(src_node->next==NULL)
	{
		eechema_error(ctx, src_node, "property node has not enough children");
		return -1;
	}

	node  = src_node;
	key   = node->str;
	node  = node->next;
	value = node->str;
	node  = node->next;

	if(attach_attrib(ctx, symref?symref:libsym, src_node, key, value)!=0)
	{
		return -1;
	}

	at      = NULL;
	justify = NULL;

	/* scan for attributes we need */
	{
		gsxl_node_t* child = node;

		for(;child;child=child->next)
		{
			if(strcmp(child->str, "effects")==0)
			{
				gsxl_node_t* child2 = child->children;

				for(;child2;child2=child2->next)
				{
					if(strcmp(child2->str, "hide")==0)
					{
						if(child2->children)
						{
							hidden =
								eechema_parse_yesno(ctx, child2->children);

							if(hidden<0)
							{
								return hidden;
							}
						}
						else
						{
							hidden = 1;
						}
					}
					else
					if(strcmp(child2->str, "justify")==0)
					{
						justify = child2->children;
					}
				}
			}
			else
			if(strcmp(child->str, "at")==0)
			{
				at = child->children;
			}
			else
			if(strcmp(child->str, "show_name")==0)
			{
				show_name = eechema_parse_yesno(ctx, child->children);

				if(show_name<0)
				{
					return show_name;
				}
			}
		}
	}

	if(show_name)
	{
		str = rnd_strdup_printf("%s: %%../A.%s%%", key, key);
	}
	else
	{
		str = rnd_strdup_printf("%%../A.%s%%", key);
	}

	dbg_printf("process floater: \"%s\"\n", str);

	t = eeschema_find_attr_text(ctx, libsym, str);

	if(t && !symref)
	{
		free(str);
		return 0;
	}

	if(!t)
	{
		static const char* property_ignore_list[] =
		{
			"show_name",
			"id",
			NULL
		};

		if(strcmp(key, "Reference")==0)
		{
			stroke = "sym-primary";
		}
		else
		{
			stroke = "sym-secondary";
		}

		t = eechema_parse_text__impl(ctx, libsym, node, "property", str,
			stroke, property_ignore_list);

		if(!t)
		{
			return -1;
		}

		if(!force_place)
		{
			t->spec1.x  = 0;
			t->spec1.y  = 0;
		}

		t->spec_rot    = 0;
		t->dyntext     = 1;
		t->hdr.floater = 1;

		if(symref)
		{
			/* if this new floater is added during a symref process, then */
			/* it must be hidden in the original group */
			TEXT_SYM_ATTR_HIDDEN(t) = 1;
		}
		else
		{
			TEXT_SYM_ATTR_HIDDEN(t) = hidden;
			csch_text_update(ctx->sheet, t, 1);
		}

		if(!force_place)
		{
			eeschema_hide_new_floater(ctx, ctx->sheet, libsym, t, symref);
		}
	}

	free(str);

	if(!symref && !force_place)
	{
		/* this is a libsym load, nothing else is needed here */
		return 0;
	}

	TEXT_SYM_ATTR_HANDLED(t) = 1;

	if(hidden)
	{
		dbg_printf("  hide floater\n");

		if(symref)
		{
			eeschema_sym_child_hide(ctx, symref, (csch_chdr_t*)t);
		}
		else
		{
			csch_text_free(t);
			return 0;
		}
	}
	else
	{
		float x, y;
		int rot;

		csch_coord_t dx;
		csch_coord_t dy;
		csch_coord_t bbw;
		csch_coord_t bbh;

		int mirx = 0;
		int miry = 0;

		if(symref)
		{
			csch_text_t* t2;

			csch_cgrp_ref_render(ctx->sheet, symref);
			csch_cgrp_ref_update(ctx->sheet, symref, 1);

			/* find the symref's copy of that object, we need it's updated bbox */
			/* (floaters are dyntexts, thus they have a different bbox!) */
			t2 = htip_get(&symref->id2obj, t->hdr.oid);

			bbw = t2->hdr.bbox.x2 - t2->hdr.bbox.x1;
			bbh = t2->hdr.bbox.y2 - t2->hdr.bbox.y1;
		}
		else
		{
			csch_text_dyntext_render(t);
			csch_text_update(ctx->sheet, t, 1);

			bbw = t->hdr.bbox.x2 - t->hdr.bbox.x1;
			bbh = t->hdr.bbox.y2 - t->hdr.bbox.y1;
		}

		if(!at)
		{
			eechema_error(ctx, src_node, "missing 'at' node from 'property'");
			return -1;
		}

		if(eechema_parse_at(ctx, at, &x, &y, &rot)!=0)
		{
			return -1;
		}

		if(symref)
		{
			dx = csch_alien_coord_x(&ctx->alien, x) - ctx->cur_sym_x;
			dy = csch_alien_coord_y(&ctx->alien, y) - ctx->cur_sym_y;
		}
		else
		{
			dx = csch_alien_coord_x(&ctx->alien, x) - t->spec1.x;
			dy = csch_alien_coord_y(&ctx->alien, y) - t->spec1.y;
		}

		/* rotate floater back to compansate parent rotation */
		switch(parent_rot)
		{
		case 0:
			break;
		case 90:
			{ csch_coord_t t = dx; dx = dy; dy = -t; }
			if(rot==90) { dx -= bbh; }
			break;
		case 180:
			dy = -dy;
			if(rot==0) { dy += bbh; dx -= bbw; }
			if(rot==90) { dx -= 2*bbh; }
			break;
		case 270:
			{ csch_coord_t t = dx; dx = -dy; dy = t; }
			if(rot==0) { dy += bbh; }
			break;
		}

		dbg_printf("  justify BB={%u,%u}, d={%i,%i}, rot=%i(+%i)\n",
			(unsigned int)bbw, (unsigned int)bbh,
			(int)dx, (int)dy, (int)rot, (int)parent_rot);

		if(180<=((rot+parent_rot)%360))
		{
			rot = (rot + 180) % 360;
		}

		if(eeschema_justify_text(ctx, justify, bbw, bbh, rot, &dx, &dy,
			&mirx, &miry))
		{
			return -1;
		}

		if(symref)
		{
			eeschema_sym_child_moverotmir(ctx, symref, (csch_chdr_t*)t,
				dx, dy, rot, mirx, miry);

			dbg_printf("  moverotmir: dx=%i, dy=%i, rot=%i, mir=%i/%i\n",
				(int)dx, (int)dy, (int)rot, (int)mirx, (int)miry);
		}
		else
		{
			dbg_printf("  BB={%u,%u}, d={%i,%i}\n",
				(unsigned int)bbw, (unsigned int)bbh,
				(int)dx, (int)dy);

			t->spec1.x   += dx;
			t->spec1.y   += dy;
			t->spec_mirx = mirx;
			t->spec_miry = miry;
			t->spec_rot  = rot;

			csch_text_update(ctx->sheet, t, 1);
		}
	}

	return 0;
}

static int eechema_parse__libsym_property(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	return eeschema_parse_property__impl(ctx, dst, NULL, node, 0, 0);
}

static int eechema_parse__libsymdata_pin(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	const char* elec_type;
	const char* graph_type;
	float       length;
	float       x, y;
	float       dx, dy;
	float       pin_ex, pin_ey;
	int         rot;

	gsxl_node_t* at;
	gsxl_node_t* len_node;
	gsxl_node_t* name;
	gsxl_node_t* number;

	gsxl_node_t* const parent = node->parent;

	csch_source_arg_t* src;

	const struct pin_decor* base_dec;
	const struct pin_decor* end_dec;

	csch_cgrp_t* pin;

	if(node->next==NULL)
	{
		eechema_error(ctx, node, "'pin' node has not enough children");
		return -1;
	}

	elec_type  = node->str;
	node       = node->next;
	graph_type = node->str;
	node       = node->next;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "at")==0)
		{
			at = node->children;
		}
		else
		if(strcmp(node->str, "length")==0)
		{
			len_node = node->children;
		}
		else
		if(strcmp(node->str, "name")==0)
		{
			name = node->children;
		}
		else
		if(strcmp(node->str, "number")==0)
		{
			number = node->children;
		}
		else
		if(strcmp(node->str, "hide")==0)
		{
			TODO("what to do for hidden pins?");
			return 0;
		}
		else
		if(strcmp(node->str, "alternate")==0)
		{
			TODO("what alternate is used for?");
		}
		else
		{
			unexpected_child(ctx, parent, node);
			return -1;
		}
	}

	if(!at || !len_node || !name || !number)
	{
		eechema_error(ctx, parent,
			"missing data (at/length/name/number) from 'pin' object");
		return -1;
	}

	if(eechema_parse_at(ctx, at, &x, &y, &rot)!=0)
	{
		return -1;
	}

	length = strtod(len_node->str, NULL);

	base_dec = eeschema_find_pin_decor(eeschema_base_pin_decors, graph_type);

	if(base_dec==NULL)
	{
		eechema_error(ctx, parent, "unknown pin graphic type: '%s'",
			graph_type);
		return -1;
	}

	end_dec = eeschema_find_pin_decor(eeschema_end_pin_decors, elec_type);

	if(end_dec==NULL)
	{
		eechema_error(ctx, parent, "unknown pin electrical type: '%s'",
			elec_type);
		return -1;
	}

	switch(rot)
	{
	case 0:   dx = +1; dy = 0;  break;
	case 90:  dx = 0;  dy = +1; break;
	case 180: dx = -1; dy = 0;  break;
	case 270: dx = 0;  dy = -1; break;
	default: abort();
	}

	pin_ex = x + dx*length;
	pin_ey = y + dy*length;

	{
		float line_ex = pin_ex - dx*base_dec->shorten;
		float line_ey = pin_ey - dy*base_dec->shorten;

		src = make_src_attrib(ctx, parent);

		pin = (csch_cgrp_t*)csch_alien_mkpin_line(&ctx->alien, src, dst,
			x, y, line_ex, line_ey);

		/* quickly render decorations */

		if(base_dec->render_decor(ctx, pin, parent, base_dec, rot, pin_ex,
			pin_ey)!=0)
		{
			eechema_error(ctx, parent, "could not render decors for pin "
				"graphic type: '%s'", graph_type);
			return -1;
		}

		if(end_dec->render_decor(ctx, pin, parent, end_dec, rot, x, y)!=0)
		{
			eechema_error(ctx, parent, "could not render decors for pin "
				"eletrical type: '%s'", elec_type);
			return -1;
		}
	}

	/* add pin number as "name" attrib */
	if(attach_attrib(ctx, pin, number, "name", number->str)!=0)
	{
		return -1;
	}

	if(ctx->cur_libsym->show_pin_numbers)
	{
		float half_x = (x + pin_ex) * 0.5;
		float half_y = (y + pin_ey) * 0.5;

		csch_text_t* text = (csch_text_t*)csch_alien_mktext(&ctx->alien, pin,
			half_x, half_y, "term-primary");

		text->text = rnd_strdup("%../a.display/name%");
		text->dyntext = 1;

		/* NOTE: it is not possible to center-align pin numbers, because */
		/* when we create them, we must use dyntext, and real content will */
		/* not be available until compilation */

		switch(rot)
		{
		case 0:
			text->spec_mirx = 1;
			break;

		case 90:
			text->spec_rot = -90;
			text->spec_mirx = 1;
			break;

		case 180:
			break;

		case 270:
			text->spec_rot = 90;
			break;

		default:
			abort();
		}
	}

	/* render pin names */
	if(ctx->cur_libsym->show_pin_names &&
		0!=(*name->str) && strcmp(name->str, "~")!=0)
	{
		float offset = ctx->cur_libsym->pin_names_offset;

		csch_text_t* const text = (csch_text_t*)csch_alien_mktext(&ctx->alien,
			pin, pin_ex+dx*offset, pin_ey+dy*offset, "sym-decor");

		text->text = rnd_strdup(name->str);

		csch_text_update(ctx->sheet, text, 1);

		offset = (text->hdr.bbox.y2 - text->hdr.bbox.y1) * 0.5;

		switch(rot)
		{
		case 0:
			text->spec1.y -= offset;
			break;

		case 90:
			text->spec_rot = 90;
			text->spec1.x += offset;
			break;

		case 180:
			text->spec_mirx = 1;
			text->spec1.y -= offset;
			break;

		case 270:
			text->spec_rot = -90;
			text->spec_mirx = 1;
			text->spec1.x += offset;
			break;

		default:
			abort();
		}
	}

	return 0;
}

static int eechema_parse__schsym_mirror(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	if(node->next!=NULL)
	{
		eechema_error(ctx, node, "invalid mirror");
		return -1;
	}

	if(strcmp(node->str, "x")==0)
	{
		/* "mirror x" means different thing in eeschema */
		ctx->cur_sym_miry = 1;
		return 0;
	}
	else
	if(strcmp(node->str, "y")==0)
	{
		/* "mirror y" means different thing in eeschema */
		ctx->cur_sym_mirx = 1;
		return 0;
	}

	eechema_error(ctx, node, "unexpected mirror option: '%s'", node->str);
	return -1;
}

static int eechema_parse__schsym_exclude_from_sim(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int res = eechema_parse_yesno(ctx, node);

	if(res<0)
	{
		return res;
	}

	ctx->cur_sym_exclude_from_sim = 1;

	if(res)
	{
		return attach_attrib(ctx, ctx->cur_sym, node, a_sim_omit, "yes");
	}

	return 0;
}

static int eechema_parse__schsym_in_bom(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int res = eechema_parse_yesno(ctx, node);

	if(res<0)
	{
		return res;
	}

	ctx->cur_sym_in_bom = 1;

	return attach_attrib(ctx, ctx->cur_sym, node, a_in_bom, node->str);
}

static int eechema_parse__schsym_on_board(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int res = eechema_parse_yesno(ctx, node);

	if(res<0)
	{
		return res;
	}

	ctx->cur_sym_on_board = 1;

	if(!res)
	{
		return attach_attrib(ctx, ctx->cur_sym, node, a_pcb_omit, "yes");
	}

	return 0;
}

static int eechema_parse__schsym_dnp(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	int res = eechema_parse_yesno(ctx, node);

	if(res<0)
	{
		return res;
	}

	if(res)
	{
		return attach_attrib(ctx, ctx->cur_sym, node, a_dnp, "yes");
	}

	return 0;
}

static int eechema_parse__schsym_property(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	csch_cgrp_t* const libsym = ctx->cur_sym->data.ref.grp;

	if(eeschema_parse_property__impl(ctx, libsym, ctx->cur_sym, node,
		ctx->cur_sym_rot, 0)!=0)
	{
		return -1;
	}

	return 0;
}

static int eechema_parse__wks_any_margin__(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node, float* const out_margin)
{
	char* end;

	if(node->next!=NULL)
	{
		unexpected_child(ctx, node->parent, node->next);
		return -1;
	}

	*out_margin = strtod(node->str, &end);

	if(end==NULL || 0!=(*end))
	{
		eechema_error(ctx, node, "value is not a valid float number: '%s'",
			node->str);
		return -1;
	}

	return 0;
}

static int eechema_parse__wks_left_margin(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	return eechema_parse__wks_any_margin__(ctx, dst, node,
		&ctx->page_left_margin);
}

static int eechema_parse__wks_right_margin(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	return eechema_parse__wks_any_margin__(ctx, dst, node,
		&ctx->page_right_margin);
}

static int eechema_parse__wks_top_margin(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	return eechema_parse__wks_any_margin__(ctx, dst, node,
		&ctx->page_top_margin);
}

static int eechema_parse__wks_bottom_margin(read_ctx_t* ctx,
	csch_cgrp_t* dst, gsxl_node_t* node)
{
	return eechema_parse__wks_any_margin__(ctx, dst, node,
		&ctx->page_bottom_margin);
}

static int eechema_parse__wks_line(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	struct eeschema_wks_helper wks;
	struct eeschema_wks_xy start_xy;
	struct eeschema_wks_xy end_xy;

	csch_cgrp_t* tb;

	gsxl_node_t* start = NULL;
	gsxl_node_t* end = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);

	eeschema_wks_helper_init(ctx, &wks);

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "start")==0)
		{
			start = node->children;
		}
		else
		if(strcmp(node->str, "end")==0)
		{
			end = node->children;
		}
		else
		{
			int res = eeschema_wks_helper_handle_node(ctx, &wks, node);

			if(res<0)
			{
				return -1;
			}
			else
			if(res)
			{
				unexpected_child(ctx, node->parent, node);
				return -1;
			}
		}
	}

	if(!start || !end)
	{
		eechema_error(ctx, parent, "missing info data from 'line' wks object");
		return -1;
	}

	if(eechema_parse_wks_xy(ctx, &wks, start, &start_xy)!=0 ||
		eechema_parse_wks_xy(ctx, &wks, end, &end_xy)!=0)
	{
		return -1;
	}

	tb = eeschema_get_titleblock(ctx);

	do
	{
		if(!csch_alien_mkline(&ctx->alien, tb, start_xy.x, start_xy.y,
			end_xy.x, end_xy.y, stroke))
		{
			eechema_error(ctx, parent, "could not create line object");
			return -1;
		}

		eeschema_wks_helper_xy_incr(&wks, &start_xy);
		eeschema_wks_helper_xy_incr(&wks, &end_xy);
	}
	while(0<(--wks.repeat) && !wks.oob);

	return 0;
}

static int eechema_parse__wks_rect(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	struct eeschema_wks_helper wks;
	struct eeschema_wks_xy start_xy;
	struct eeschema_wks_xy end_xy;

	csch_cgrp_t* tb;

	gsxl_node_t* start = NULL;
	gsxl_node_t* end = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);

	eeschema_wks_helper_init(ctx, &wks);

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "start")==0)
		{
			start = node->children;
		}
		else
		if(strcmp(node->str, "end")==0)
		{
			end = node->children;
		}
		else
		{
			int res = eeschema_wks_helper_handle_node(ctx, &wks, node);

			if(res<0)
			{
				return -1;
			}
			else
			if(res)
			{
				unexpected_child(ctx, node->parent, node);
				return -1;
			}
		}
	}

	if(!start || !end)
	{
		eechema_error(ctx, parent, "missing info data from 'rect' wks object");
		return -1;
	}

	if(eechema_parse_wks_xy(ctx, &wks, start, &start_xy)!=0 ||
		eechema_parse_wks_xy(ctx, &wks, end, &end_xy)!=0)
	{
		return -1;
	}

	tb = eeschema_get_titleblock(ctx);

	do
	{
		if(!csch_alien_mkrect(&ctx->alien, tb, start_xy.x, start_xy.y,
			end_xy.x, end_xy.y, stroke, NULL))
		{
			eechema_error(ctx, parent, "could not create rect object");
			return -1;
		}

		eeschema_wks_helper_xy_incr(&wks, &start_xy);
		eeschema_wks_helper_xy_incr(&wks, &end_xy);
	}
	while(0<(--wks.repeat) && !wks.oob);

	return 0;
}

static int eechema_parse__wks_tbtext(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	struct eeschema_wks_helper wks;
	struct eeschema_wks_xy pos_xy;
	struct eeschema_wks_text wks_text;

	csch_cgrp_t* tb;

	gsxl_node_t* pos = NULL;

	int jleft   = 1;
	int jright  = 0;
	int jtop    = 0;
	int jbottom = 0;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);

	eeschema_wks_helper_init(ctx, &wks);

	/* first node is the text, remove it */
	eeschema_wks_text_init(&wks_text, node->str);
	node = node->next;

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "pos")==0)
		{
			pos = node->children;
		}
		else
		if(strcmp(node->str, "font")==0)
		{
			/* ignore it for now */
		}
		else
		if(strcmp(node->str, "justify")==0)
		{
			gsxl_node_t* child = node->children;

			jleft  = 0;
			jright = 0;

			if(strcmp(child->str, "left")==0)
			{
				jleft = 1;
			}
			else
			if(strcmp(child->str, "right")==0)
			{
				jright = 1;
			}
			else
			if(strcmp(child->str, "center")==0)
			{
				/* nop */
			}
			else
			{
				unexpected_child(ctx, node, child);
				return -1;
			}

			child = child->next;

			if(child)
			{
				jtop    = 0;
				jbottom = 0;

				if(strcmp(child->str, "top")==0)
				{
					jtop = 1;
				}
				else
				if(strcmp(child->str, "bottom")==0)
				{
					jbottom = 1;
				}
				else
				if(strcmp(child->str, "center")==0)
				{
					/* nop */
				}
				else
				{
					unexpected_child(ctx, node, child);
					return -1;
				}
			}
		}
		else
		{
			int res = eeschema_wks_helper_handle_node(ctx, &wks, node);

			if(res<0)
			{
				return -1;
			}
			else
			if(res)
			{
				unexpected_child(ctx, node->parent, node);
				return -1;
			}
		}
	}

	if(!pos)
	{
		eechema_error(ctx, parent,
			"missing info data from 'tbtext' wks object");
		return -1;
	}

	if(eechema_parse_wks_xy(ctx, &wks, pos, &pos_xy)!=0)
	{
		return -1;
	}

	tb = eeschema_get_titleblock(ctx);

	do
	{
		csch_text_t* const text = (csch_text_t*)csch_alien_mktext(&ctx->alien,
			tb, pos_xy.x, pos_xy.y, stroke);

		int is_dyntext;

		if(!text)
		{
			eechema_error(ctx, parent, "could not create text object");
			return -1;
		}

		text->text = eeschema_wks_text_render(ctx, parent, wks_text.text,
			&is_dyntext);

		text->dyntext = is_dyntext;

		csch_text_update(ctx->sheet, text, 1);

		if(jright)
		{
			text->spec_mirx = 1;
		}
		else
		if(!jleft)
		{
			/* center align, works only for non-dyntext type */

			if(!text->dyntext)
			{
				text->spec1.x -= (text->hdr.bbox.x2 - text->hdr.bbox.x1)/2;
			}
		}

		if(jtop)
		{
			text->spec1.y -= text->hdr.bbox.y2 - text->hdr.bbox.y1;
		}
		else
		if(!jbottom)
		{
			text->spec1.y -= (text->hdr.bbox.y2 - text->hdr.bbox.y1)/2;
		}

		eeschema_wks_helper_xy_incr(&wks, &pos_xy);
		eeschema_wks_text_incr(&wks_text);
	}
	while(0<(--wks.repeat) && !wks.oob);

	return 0;
}

static int eechema_parse__wks_polygon(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	struct eeschema_wks_helper wks;
	struct eeschema_wks_xy pos_xy;

	csch_cgrp_t* tb;

	gsxl_node_t* pos = NULL;
	gsxl_node_t* pts = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);
	const char* const fill = eeschema_get_fill(ctx);

	eeschema_wks_helper_init(ctx, &wks);

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "pos")==0)
		{
			pos = node->children;
		}
		else
		if(strcmp(node->str, "pts")==0)
		{
			pts = node->children;
		}
		else
		{
			int res = eeschema_wks_helper_handle_node(ctx, &wks, node);

			if(res<0)
			{
				return -1;
			}
			else
			if(res)
			{
				unexpected_child(ctx, node->parent, node);
				return -1;
			}
		}
	}

	if(!pts)
	{
		eechema_error(ctx, parent,
			"missing info data from 'polygon' wks object");
		return -1;
	}

	if(eechema_parse_wks_xy(ctx, &wks, pos, &pos_xy)!=0)
	{
		return -1;
	}

	tb = eeschema_get_titleblock(ctx);

	do
	{
		if(!eeschema_render_polyline(ctx, tb, parent, pts, stroke, fill,
			1, 1, pos_xy.x, pos_xy.y))
		{
			eechema_error(ctx, parent, "could not create polygon object");
			return -1;
		}

		eeschema_wks_helper_xy_incr(&wks, &pos_xy);
	}
	while(0<(--wks.repeat) && !wks.oob);

	return 0;
}

static int eechema_parse__wks_bitmap(read_ctx_t* ctx, csch_cgrp_t* dst,
	gsxl_node_t* node)
{
	struct eeschema_wks_helper wks;
	struct eeschema_wks_xy pos_xy;

	csch_cgrp_t* tb;

	gsxl_node_t* pos = NULL;

	gsxl_node_t* const parent = node->parent;

	const char* const stroke = eeschema_get_stroke(ctx);

	eeschema_wks_helper_init(ctx, &wks);

	for(;node;node=node->next)
	{
		if(strcmp(node->str, "pos")==0)
		{
			pos = node->children;
		}
		else
		if(strcmp(node->str, "scale")==0 ||
			strcmp(node->str, "data")==0)
		{
			/* ignore */
		}
		else
		{
			int res = eeschema_wks_helper_handle_node(ctx, &wks, node);

			if(res<0)
			{
				return -1;
			}
			else
			if(res)
			{
				unexpected_child(ctx, node->parent, node);
				return -1;
			}
		}
	}

	if(eechema_parse_wks_xy(ctx, &wks, pos, &pos_xy)!=0)
	{
		return -1;
	}

	tb = eeschema_get_titleblock(ctx);

	do
	{
		if(!eeschema_render_image_placeholder(ctx, tb, parent,
			pos_xy.x, pos_xy.y, stroke))
		{
			eechema_error(ctx, parent, "could not create image object");
			return -1;
		}

		eeschema_wks_helper_xy_incr(&wks, &pos_xy);
	}
	while(0<(--wks.repeat) && !wks.oob);

	return 0;
}

#define DRAWING_PRIMITIVES \
	{ "polyline", eechema_parse_polyline }, \
	{ "rectangle", eechema_parse_rectangle }, \
	{ "text", eechema_parse_text }, \
	{ "circle", eechema_parse_circle }, \
	{ "arc", eechema_parse_arc }, \
	{ "bezier", eechema_parse_bezier }, \
	{ "image", eechema_parse_image }, \

static const dispatch_t eechema_disptab_sch[] =
{
	{ "version", eechema_parse__sch_version },
	{ "generator", eechema_parse__sch_attach_attr },
	{ "generator_version", eechema_parse__sch_attach_attr },
	{ "uuid", eechema_parse__sch_uuid },
	{ "paper", eechema_parse__sch_paper },
	{ "title_block", eechema_parse__sch_titleblock },
	{ "lib_symbols", eechema_parse__sch_lib_symbols },
	{ "symbol", eechema_parse__sch_symbol },
	{ "wire", eechema_parse__sch_wire },
	{ "bus", eechema_parse__sch_bus },
	{ "label", eechema_parse__sch_label },
	{ "sheet_instances", eechema_parse__ignore }, /* TODO */
	{ "symbol_instances", eechema_parse__ignore }, /* TODO */
	{ "junction", eechema_parse__ignore },
	{ "no_connect", eechema_parse_noconnect },
	{ "global_label", eechema_parse_globallabel },
	{ "hierarchical_label", eechema_parse_hierarchicallabel },
	{ "netclass_flag", eechema_parse_netclassflag },
	{ "bus_entry", eechema_parse_busentry },
	{ "bus_alias", eechema_parse__ignore }, /* TODO */
	{ "sheet", eechema_parse__sch_sheet },
	{ "embedded_fonts", eechema_parse__ignore }, /* TODO: what's the purpose of this? */
	DRAWING_PRIMITIVES
	{ 0 }
};

/* common libsym part, will be processed first */
static const dispatch_t eechema_disptab_libsym0[] =
{
	{ "pin_numbers", eechema_parse__libsym_pin_numbers },
	{ "pin_names", eechema_parse__libsym_pin_names },
	{ "exclude_from_sim", eechema_parse__libsym_exclude_from_sim },
	{ "in_bom", eechema_parse__libsym_in_bom },
	{ "on_board", eechema_parse__libsym_on_board },
	{ "property", eechema_parse__libsym_property },
	{ "power", eechema_parse__ignore },
	{ "embedded_fonts", eechema_parse__ignore }, /* TODO: what's the purpose of this? */

	{ "symbol", eechema_parse__ignore_silent },
	{ 0 }
};

/* libsym symbol-specific part, will be processed later */
static const dispatch_t eechema_disptab_libsym1[] =
{
	{ "symbol", eechema_parse__libsym_symbol },

	{ "pin_numbers", eechema_parse__ignore_silent },
	{ "pin_names", eechema_parse__ignore_silent },
	{ "exclude_from_sim", eechema_parse__ignore_silent },
	{ "in_bom", eechema_parse__ignore_silent },
	{ "on_board", eechema_parse__ignore_silent },
	{ "property", eechema_parse__ignore_silent },
	{ "power", eechema_parse__ignore_silent },
	{ "embedded_fonts", eechema_parse__ignore_silent },
	{ 0 }
};

static const dispatch_t eechema_disptab_libsymdata[] =
{
	{ "pin", eechema_parse__libsymdata_pin },
	DRAWING_PRIMITIVES
	{ 0 }
};

#undef DRAWING_PRIMITIVES

static const dispatch_t eechema_disptab_titleblock[] =
{
	{ "title", eechema_parse__sch_attach_attr },
	{ "date", eechema_parse__sch_attach_attr },
	{ "rev", eechema_parse__sch_attach_attr },
	{ "company", eechema_parse__sch_attach_attr },
	{ "comment", eechema_parse__sch_attach_attr2 },
	{ 0 }
};

static const dispatch_t eechema_disptab_schsymdata[] =
{
	{ "lib_id", eechema_parse__ignore_silent }, /* already processed! */
	{ "lib_name", eechema_parse__ignore_silent }, /* already processed! */
	{ "at", eechema_parse__ignore_silent }, /* already processed! */
	{ "mirror", eechema_parse__schsym_mirror },
	{ "unit", eechema_parse__ignore_silent }, /* already processed! */
	{ "exclude_from_sim", eechema_parse__schsym_exclude_from_sim },
	{ "in_bom", eechema_parse__schsym_in_bom },
	{ "on_board", eechema_parse__schsym_on_board },
	{ "dnp", eechema_parse__schsym_dnp },
	{ "fields_autoplaced", eechema_parse__ignore_silent },
	{ "uuid", eechema_parse__ignore_silent }, /* already processed! */
	{ "property", eechema_parse__schsym_property },
	{ "pin", eechema_parse__ignore }, /* this subtree has only one (uuid) node */
	{ "instances", eechema_parse__ignore }, /* TODO? */
	{ 0 }
};

static const dispatch_t eechema_disptab_wkssetup[] =
{
	{ "left_margin", eechema_parse__wks_left_margin },
	{ "right_margin", eechema_parse__wks_right_margin },
	{ "top_margin", eechema_parse__wks_top_margin },
	{ "bottom_margin", eechema_parse__wks_bottom_margin },
	{ "textsize", eechema_parse__ignore },
	{ "linewidth", eechema_parse__ignore },
	{ "textlinewidth", eechema_parse__ignore },
	{ 0 }
};

static const dispatch_t eechema_disptab_wks[] =
{
	{ "line", eechema_parse__wks_line },
	{ "rect", eechema_parse__wks_rect },
	{ "tbtext", eechema_parse__wks_tbtext },
	{ "polygon", eechema_parse__wks_polygon },
	{ "bitmap", eechema_parse__wks_bitmap },
	{ "version", eechema_parse__ignore },
	{ "generator", eechema_parse__ignore },
	{ "generator_version", eechema_parse__ignore },
	{ "setup", eechema_parse__ignore },
	{ 0 }
};
