/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre, Cyril Concolato
 *			Copyright (c) Telecom ParisTech 2000-2023
 *					All rights reserved
 *
 *  This file is part of GPAC / Scene Management sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <gpac/scene_manager.h>
#include <gpac/constants.h>
#include <gpac/utf.h>
#include <gpac/xml.h>
#include <gpac/events.h>
#include <gpac/internal/compositor_dev.h>
#include <gpac/internal/scenegraph_dev.h>
#include <gpac/internal/laser_dev.h>
#include <gpac/nodes_svg.h>
#include <gpac/base_coding.h>

#ifndef GPAC_DISABLE_SVG

typedef struct _st_entry
{
	struct _st_entry *next;
	/*as referred to by xlink-href*/
	char *stream_name;
	/*stream id*/
	u32 id;
	const char *nhml_info;
} SVG_SAFExternalStream;

typedef struct
{
	GF_SceneLoader *load;
	GF_Err last_error;
	GF_SAXParser *sax_parser;
	u32 has_root;

	/* stack of SVG nodes*/
	GF_List *node_stack;

	GF_List *deferred_hrefs;
	GF_List *deferred_animations;
	GF_List *deferred_listeners;
	/*non-linear parsing*/
	GF_List *peeked_nodes;

	/*LASeR parsing*/
	u32 command_depth;
	GF_StreamContext *laser_es;
	GF_AUContext *laser_au;
	GF_Command *command;

	/*SAF AU maps to OD AU and is used for each new media declaration*/
	GF_AUContext *saf_au;
	GF_StreamContext *saf_es;

	SVG_SAFExternalStream *streams;

	/*the namespace of the parent element. This is a shortcut to avoid querying the namespace list stored
	in the scene graph to find out the namespace of the current element.
	For example in <foo:bar><foo:test/></foo:bar>, it avoids looking for the namespace when parsing <foo:test>
	*/
	u32 current_ns;

	/*if parser is used to parse a fragment, the root of the fragment is stored here*/
	GF_Node *fragment_root;
} GF_SVG_Parser;

typedef struct {
	/* Stage of the resolving:
	    0: resolving attributes which depends on the target: from, to, by, values, type
		1: resolving begin times
		2: resolving end times */
	u32 resolve_stage;
	/* Animation element being deferred */
	SVG_Element *animation_elt;
	/* anim parent*/
	SVG_Element *anim_parent;
	/* target animated element*/
	SVG_Element *target;
	/* id of the target element when unresolved*/
	char *target_id;

	/* attributes which cannot be parsed until the type of the target attribute is known */
	char *type; /* only for animateTransform */
	char *to;
	char *from;
	char *by;
	char *values;
} SVG_DeferredAnimation;

typedef struct
{
	/*top of parsed sub-tree*/
	SVG_Element *node;
	/*the current element is animation that cannot be parsed completely
	  upon reception of start tag of element but for which we may be able
	  to parse at the end tag of the element (animateMotion)*/
	SVG_DeferredAnimation *anim;
	/*depth of unknown elements being skipped*/
	u32 unknown_depth;
	/*last child added, used to speed-up parsing*/
	GF_ChildNodeItem *last_child;

	/*the namespace of the parent element (used for restoring the context after the current node has been parsed)*/
	u32 current_ns;
	Bool has_ns;
} SVG_NodeStack;

static GF_Err svg_report(GF_SVG_Parser *parser, GF_Err e, char *format, ...)
{
#ifndef GPAC_DISABLE_LOG
	if (format && gf_log_tool_level_on(GF_LOG_PARSER, e ? GF_LOG_ERROR : GF_LOG_WARNING)) {
		char szMsg[2048];
		va_list args;
		va_start(args, format);
		vsnprintf(szMsg, 2048, format, args);
		va_end(args);
		GF_LOG((u32) (e ? GF_LOG_ERROR : GF_LOG_WARNING), GF_LOG_PARSER, ("[SVG Parsing] line %d - %s\n", gf_xml_sax_get_line(parser->sax_parser), szMsg));
	}
#endif
	if (e) {
		parser->last_error = e;
		gf_xml_sax_suspend(parser->sax_parser, GF_TRUE);
	}
	return e;
}

static void svg_progress(void *cbk, u64 done, u64 total)
{
	GF_SVG_Parser *parser = (GF_SVG_Parser *)cbk;

	/*notify MediaEvent*/
	if (parser->load && parser->load->is) {
		parser->load->is->on_media_event(parser->load->is, GF_EVENT_MEDIA_PROGRESS);
		if (done == total) {
			parser->load->is->on_media_event(parser->load->is, GF_EVENT_MEDIA_LOAD_DONE);
		}
	}
	gf_set_progress("SVG (Dynamic Attribute List) Parsing", done, total);
}

static SVG_SAFExternalStream *svg_saf_get_stream(GF_SVG_Parser *parser, u32 id, const char *name)
{
	SVG_SAFExternalStream *st;
	st = parser->streams;
	while (st) {
		if (id == st->id) return st;
		if (name && st->stream_name && !strcmp(name, st->stream_name) ) return st;
		st = st->next;
	}
	return NULL;
}

static SVG_SAFExternalStream *svg_saf_get_next_available_stream(GF_SVG_Parser *parser)
{
	/*1 reserved for base laser stream*/
	u32 id = 1;
	SVG_SAFExternalStream *st = parser->streams;
	SVG_SAFExternalStream *prev = NULL;

	while (st) {
		prev = st;
		id = st->id;
		st = st->next;
	}
	GF_SAFEALLOC(st, SVG_SAFExternalStream);
	if (!st) return NULL;

	if (prev) prev->next = st;
	else parser->streams = st;
	st->id = id+1;
	return st;
}

static void svg_lsr_set_v2(GF_SVG_Parser *parser)
{
	u32 i;
	if (parser->load->ctx && parser->load->ctx->root_od) {
		for (i=0; i<gf_list_count(parser->load->ctx->root_od->ESDescriptors); i++) {
			GF_ESD *esd = (GF_ESD *)gf_list_get(parser->load->ctx->root_od->ESDescriptors, i);
			if (esd->decoderConfig->streamType==GF_STREAM_SCENE) {
				GF_LASERConfig *cfg = (GF_LASERConfig *)esd->decoderConfig->decoderSpecificInfo;
				if (cfg && (cfg->tag==GF_ODF_LASER_CFG_TAG)) {
					if (!cfg->extensionIDBits) cfg->extensionIDBits = 2;
				}
			}
		}
	}
}


static void svg_process_media_href(GF_SVG_Parser *parser, GF_Node *elt, XMLRI *iri)
{
	u32 tag = gf_node_get_tag(elt);

	if ((tag==TAG_SVG_image) || (tag==TAG_SVG_video) || (tag==TAG_SVG_audio)) {
		SVG_SAFExternalStream *st = svg_saf_get_stream(parser, 0, iri->string+1);
		if (!st && !strnicmp(iri->string, "stream:", 7))
			st = svg_saf_get_stream(parser, 0, iri->string+7);
		if (st) {
			gf_free(iri->string);
			iri->string = NULL;
			iri->lsr_stream_id = st->id;
			iri->type = XMLRI_STREAMID;
			return;
		}
	}
	if ((parser->load->flags & GF_SM_LOAD_EMBEDS_RES) && (iri->type==XMLRI_STRING) ) {
		u32 size;
		char *buffer;

		GF_Err e = gf_file_load_data(iri->string, (u8 **) &buffer, &size);
		if (e) return;

		if (tag==TAG_SVG_script) {
			GF_DOMText *dtext;
			GF_DOMAttribute *att, *prev;
			buffer[size]=0;
			dtext = gf_dom_add_text_node(elt, buffer);
			dtext->type = GF_DOM_TEXT_CDATA;

			gf_free(iri->string);
			iri->string=NULL;

			/*delete attribute*/
			att = ((GF_DOMNode*)elt)->attributes;
			prev = NULL;
			while(att) {
				if (att->tag!=TAG_XLINK_ATT_href) {
					prev = att;
					att = att->next;
					continue;
				}
				gf_svg_delete_attribute_value(att->data_type, att->data, elt->sgprivate->scenegraph);
				if (prev) prev->next = att->next;
				else ((GF_DOMNode*)elt)->attributes = att->next;
				gf_free(att);
				break;
			}
		} else {
			char *mtype;
			char *buf64;
			u32 size64 = size*2 + 3;
			char *ext;
			buf64 = (char *)gf_malloc(sizeof(char) * size64);
			size64 = gf_base64_encode(buffer, (u32)size, buf64, size64);
			buf64[size64] = 0;
			mtype = "application/data";
			ext = strchr(iri->string, '.');
			if (ext) {
				if (!stricmp(ext, ".png")) mtype = "image/png";
				if (!stricmp(ext, ".jpg") || !stricmp(ext, ".jpeg")) mtype = "image/jpg";
			}
			gf_free(iri->string);
			iri->string = (char *)gf_malloc(sizeof(char)*(40+(size_t)size64));
			sprintf(iri->string, "data:%s;base64,%s", mtype, buf64);
			gf_free(buf64);
			gf_free(buffer);
		}
	}
}

static void xsr_exec_command_list(GF_Node *node, void *par, Bool is_destroy)
{
	GF_DOMUpdates *up = (GF_DOMUpdates *)node;
	if (is_destroy || !up || (up->sgprivate->tag!=TAG_DOMUpdates)) return;
	gf_sg_command_apply_list(up->sgprivate->scenegraph, up->updates, 0);
}

static GF_Node *svg_find_node(GF_SVG_Parser *parser, char *ID)
{
	u32 i, count, tag;
	char *node_class;
	GF_Node *n = gf_sg_find_node_by_name(parser->load->scene_graph, ID);
	if (n) return n;

	count = gf_list_count(parser->peeked_nodes);
	for (i=0; i<count; i++) {
		n = (GF_Node*)gf_list_get(parser->peeked_nodes, i);
		if (!strcmp(gf_node_get_name(n), ID)) return n;
	}
	node_class = gf_xml_sax_peek_node(parser->sax_parser, "id", ID, NULL, NULL, NULL, NULL);
	if (!node_class) return NULL;

	tag = gf_xml_get_element_tag(node_class, parser->current_ns);
	n = gf_node_new(parser->load->scene_graph, tag);

	gf_free(node_class);

	if (n) {
		gf_svg_parse_element_id(n, ID, GF_FALSE);
		gf_list_add(parser->peeked_nodes, n);
	}
	return n;
}

static void svg_post_process_href(GF_SVG_Parser *parser, GF_Node *elt, XMLRI *iri)
{
	GF_Err e;

	/* Embed script if needed or clean data URL with proper mime type */
	svg_process_media_href(parser, elt, iri);

	/*keep data when encoding*/
	if ( !(parser->load->flags & GF_SM_LOAD_FOR_PLAYBACK)) return;

	/*unresolved, queue it...*/
	if ((iri->type==XMLRI_ELEMENTID) && !iri->target && iri->string) {
		gf_list_add(parser->deferred_hrefs, iri);
	}
	if (iri->type != XMLRI_STRING) return;
	e = gf_node_store_embedded_data(iri, parser->load->localPath, parser->load->fileName);
	if (e)
		svg_report(parser, e, "Error storing embedded IRI data");
}

static void svg_delete_deferred_anim(SVG_DeferredAnimation *anim, GF_List *deferred_animations)
{
	if (deferred_animations) gf_list_del_item(deferred_animations, anim);

	if (anim->animation_elt) gf_node_unregister((GF_Node*)anim->animation_elt, NULL);
	if (anim->target_id) gf_free(anim->target_id);
	if (anim->to) gf_free(anim->to);
	if (anim->from) gf_free(anim->from);
	if (anim->by) gf_free(anim->by);
	if (anim->values) gf_free(anim->values);
	if (anim->type) gf_free(anim->type);
	gf_free(anim);
}

static Bool svg_parse_animation(GF_SVG_Parser *parser, GF_SceneGraph *sg, SVG_DeferredAnimation *anim, const char *nodeID, u32 force_type)
{
	GF_FieldInfo info;
	u32 tag;
	u8 anim_value_type = 0;

	if (!anim->animation_elt)
		return GF_FALSE;
	if (anim->resolve_stage==0) {
		/* Stage 0: parsing the animation attribute values
					for that we need to resolve the target first */

		/* if we don't have a target, try to get it */
		if (!anim->target && anim->target_id)
			anim->target = (SVG_Element *) gf_sg_find_node_by_name(sg, anim->target_id + 1);

		/* if now we have a target, create the xlink:href attribute on the animation element and set it to the found target */
		if (anim->target) {
			XMLRI *iri;
			gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_XLINK_ATT_href, GF_TRUE, GF_FALSE, &info);
			iri = (XMLRI *)info.far_ptr;
			iri->type = XMLRI_ELEMENTID;
			iri->target = anim->target;
			gf_node_register_iri(sg, iri);
		}

		tag = gf_node_get_tag((GF_Node *)anim->animation_elt);
		/* get the attribute name attribute if specified */
		if (anim->type && (tag== TAG_SVG_animateTransform) ) {
			gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_transform_type, GF_TRUE, GF_FALSE, &info);
			gf_svg_parse_attribute((GF_Node *)anim->animation_elt, &info, anim->type, 0);
			switch(*(SVG_TransformType *) info.far_ptr) {
			case SVG_TRANSFORM_TRANSLATE:
				anim_value_type = SVG_Transform_Translate_datatype;
				break;
			case SVG_TRANSFORM_SCALE:
				anim_value_type = SVG_Transform_Scale_datatype;
				break;
			case SVG_TRANSFORM_ROTATE:
				anim_value_type = SVG_Transform_Rotate_datatype;
				break;
			case SVG_TRANSFORM_SKEWX:
				anim_value_type = SVG_Transform_SkewX_datatype;
				break;
			case SVG_TRANSFORM_SKEWY:
				anim_value_type = SVG_Transform_SkewY_datatype;
				break;
			case SVG_TRANSFORM_MATRIX:
				anim_value_type = SVG_Transform_datatype;
				break;
			default:
				svg_report(parser, GF_OK, "unknown datatype for animate transform");
				return GF_FALSE;
			}
		}
		else if (gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_attributeName, GF_FALSE, GF_FALSE, &info) == GF_OK) {
			SMIL_AttributeName *attname = (SMIL_AttributeName *)info.far_ptr;

			/*parse the attribute name even if the target is not found, because a namespace could be specified and
			only valid for the current node*/
			if (!attname->type) {
				char *sep;
				char *name = attname->name;
				sep = strchr(name, ':');
				if (sep) {
					sep[0] = 0;
					attname->type = gf_sg_get_namespace_code(anim->animation_elt->sgprivate->scenegraph, name);
					sep[0] = ':';
					name = gf_strdup(sep+1);
					gf_free(attname->name);
					attname->name = name;
				} else {
					attname->type = parser->current_ns;
				}
			}

			/* the target is still not known stay in stage 0 */
			if (!anim->target) return GF_FALSE;

			gf_node_get_attribute_by_name((GF_Node *)anim->target, attname->name, attname->type, GF_TRUE, GF_TRUE, &info);
			/*set the tag value to avoid parsing the name in the anim node_init phase*/
			attname->tag = info.fieldIndex;
			attname->type = 0;
			anim_value_type = info.fieldType;
		} else {
			if (tag == TAG_SVG_animateMotion) {
				anim_value_type = SVG_Motion_datatype;
			} else if (tag == TAG_SVG_discard) {
				/* there is no value to parse in discard, we can jump to the next stage */
				anim->resolve_stage = 1;
				return svg_parse_animation(parser, sg, anim, nodeID, 0);
			} else {
				svg_report(parser, GF_OK, "Missing attributeName attribute on %s", gf_node_get_name((GF_Node *)anim->animation_elt));
				return GF_FALSE;
			}
		}

		/* the target is still not known stay in stage 0 */
		if (!anim->target) return GF_FALSE;

		if (anim->to) {
			/* now that we have a target, if there is a to value to parse, create the attribute and parse it */
			gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_to, GF_TRUE, GF_FALSE, &info);
			if (!info.name) info.name = "to";
			gf_svg_parse_attribute((GF_Node *)anim->animation_elt, &info, anim->to, anim_value_type);
			if (anim_value_type==XMLRI_datatype) {
				svg_post_process_href(parser, (GF_Node *) anim->target, (XMLRI*)((SMIL_AnimateValue *)info.far_ptr)->value);
			}
		}
		if (anim->from) {
			/* now that we have a target, if there is a from value to parse, create the attribute and parse it */
			gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_from, GF_TRUE, GF_FALSE, &info);
			if (!info.name) info.name = "from";
			gf_svg_parse_attribute((GF_Node *)anim->animation_elt, &info, anim->from, anim_value_type);
			if (anim_value_type==XMLRI_datatype)
				svg_post_process_href(parser,  (GF_Node *) anim->target, (XMLRI*)((SMIL_AnimateValue *)info.far_ptr)->value);
		}
		if (anim->by) {
			/* now that we have a target, if there is a by value to parse, create the attribute and parse it */
			gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_by, GF_TRUE, GF_FALSE, &info);
			if (!info.name) info.name = "by";
			gf_svg_parse_attribute((GF_Node *)anim->animation_elt, &info, anim->by, anim_value_type);
			if (anim_value_type==XMLRI_datatype)
				svg_post_process_href(parser,  (GF_Node *) anim->target, (XMLRI*)((SMIL_AnimateValue *)info.far_ptr)->value);
		}
		if (anim->values) {
			/* now that we have a target, if there is a 'values' value to parse, create the attribute and parse it */
			gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_values, GF_TRUE, GF_FALSE, &info);
			if (!info.name) info.name = "values";
			gf_svg_parse_attribute((GF_Node *)anim->animation_elt, &info, anim->values, anim_value_type);
			if (anim_value_type==XMLRI_datatype) {
				u32 i, count;
				SMIL_AnimateValues *anim_values;
				anim_values = (SMIL_AnimateValues *)info.far_ptr;
				count = gf_list_count(anim_values->values);
				for (i=0; i<count; i++) {
					XMLRI *iri = (XMLRI *)gf_list_get(anim_values->values, i);
					svg_post_process_href(parser,  (GF_Node *) anim->target, iri);
				}
			}
		}
		anim->resolve_stage = 1;
	}

	if (anim->resolve_stage == 1) {
		/* Stage 1: parsing the begin values
					we go into the next stage only if at least one begin value is resolved */
		gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_begin, GF_TRUE, GF_FALSE, &info);
		if (gf_svg_resolve_smil_times((GF_Node *)anim->animation_elt, anim->target, *(GF_List **)info.far_ptr, GF_FALSE, nodeID)) {
			anim->resolve_stage = 2;
		} else if (force_type!=2) {
			return GF_FALSE;
		}
	}

	gf_node_get_attribute_by_tag((GF_Node *)anim->animation_elt, TAG_SVG_ATT_end, GF_TRUE, GF_FALSE, &info);
	if (!gf_svg_resolve_smil_times((GF_Node *)anim->animation_elt, anim->target, *(GF_List **)info.far_ptr, GF_TRUE, nodeID)) {
		if (force_type!=2) return GF_FALSE;
	}

	/*animateMotion needs its children to be parsed before it can be initialized !! */
	if (force_type || gf_node_get_tag((GF_Node *)anim->animation_elt) != TAG_SVG_animateMotion) {
		gf_node_init((GF_Node *)anim->animation_elt);
		return GF_TRUE;
	} else {
		return GF_FALSE;
	}

}

static void svg_resolved_refs(GF_SVG_Parser *parser, GF_SceneGraph *sg, const char *nodeID)
{
	u32 count, i;

	/*check unresolved HREF*/
	count = gf_list_count(parser->deferred_hrefs);
	for (i=0; i<count; i++) {
		GF_Node *targ;
		XMLRI *iri = (XMLRI *)gf_list_get(parser->deferred_hrefs, i);
		if (nodeID && strcmp(iri->string + 1, nodeID)) continue;
		targ = gf_sg_find_node_by_name(sg, iri->string + 1);
		if (targ) {
			iri->type = XMLRI_ELEMENTID;
			iri->target = targ;
			gf_node_register_iri(sg, iri);
			gf_free(iri->string);
			iri->string = NULL;
			gf_list_rem(parser->deferred_hrefs, i);
			i--;
			count--;
		}
	}

	/*check unresolved listeners */
	count = gf_list_count(parser->deferred_listeners);
	for (i=0; i<count; i++) {
		GF_FieldInfo info;
		GF_Node *par;
		SVG_Element *listener = (SVG_Element *)gf_list_get(parser->deferred_listeners, i);

		par = NULL;
		if (gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_observer, GF_FALSE, GF_FALSE, &info) == GF_OK) {
			XMLRI *observer = (XMLRI *)info.far_ptr;
			if (observer->type == XMLRI_ELEMENTID) {
				if (!observer->target && observer->string && !strcmp(observer->string, nodeID) ) {
					observer->target = gf_sg_find_node_by_name(sg, (char*) nodeID);
				}
				par = (GF_Node *)observer->target;
			}
			if (!par) continue;
		}
		if (gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_target, GF_FALSE, GF_FALSE, &info) == GF_OK) {
			XMLRI *target = (XMLRI *)info.far_ptr;
			if (target->type == XMLRI_ELEMENTID) {
				if (!target->target) continue;
				else {
					if (!par) par = (GF_Node*)target->target;
				}
			}
		}
		if (par) {
			gf_node_dom_listener_add((GF_Node *)par, (GF_Node *) listener);
		} else {
			gf_assert(0);
		}
		gf_list_rem(parser->deferred_listeners, i);
		i--;
		count--;
	}

	/*check unresolved anims*/
	count = gf_list_count(parser->deferred_animations);
	for (i=0; i<count; i++) {
		SVG_DeferredAnimation *anim = (SVG_DeferredAnimation *)gf_list_get(parser->deferred_animations, i);
		/*resolve it - we don't check the name since it may be used in SMIL times, which we don't track at anim level*/
		if (svg_parse_animation(parser, sg, anim, nodeID, 1)) {
			svg_delete_deferred_anim(anim, parser->deferred_animations);
			i--;
			count--;
		}
	}
}

static void svg_init_root_element(GF_SVG_Parser *parser, SVG_Element *root_svg)
{
	GF_FieldInfo width_info, height_info;
	u32 svg_w, svg_h;
	svg_w = svg_h = 0;
	if (!gf_node_get_attribute_by_tag((GF_Node *)root_svg, TAG_SVG_ATT_width, GF_FALSE, GF_FALSE, &width_info)
	        && !gf_node_get_attribute_by_tag((GF_Node *)root_svg, TAG_SVG_ATT_height, GF_FALSE, GF_FALSE, &height_info)) {
		SVG_Length * w = (SVG_Length *)width_info.far_ptr;
		SVG_Length * h = (SVG_Length *)height_info.far_ptr;
		if (w->type == SVG_NUMBER_VALUE) svg_w = FIX2INT(w->value);
		if (h->type == SVG_NUMBER_VALUE) svg_h = FIX2INT(h->value);
		gf_sg_set_scene_size_info(parser->load->scene_graph, svg_w, svg_h, GF_TRUE);
		if (parser->load->ctx) {
			parser->load->ctx->scene_width = svg_w;
			parser->load->ctx->scene_height = svg_h;
		}
	}
	if (parser->load->type == GF_SM_LOAD_XSR) {
		if (parser->command) {
			gf_assert(parser->command->tag == GF_SG_LSR_NEW_SCENE);
			parser->command->node = (GF_Node *)root_svg;
		}
	}
	gf_sg_set_root_node(parser->load->scene_graph, (GF_Node *)root_svg);
	parser->has_root = 1;
}

static void svg_check_namespace(GF_SVG_Parser *parser, const GF_XMLAttribute *attributes, u32 nb_attributes, Bool *has_ns)
{
	u32 i;
	/*parse all att to check for namespaces, to:
	   - add the prefixed namespaces (xmlns:xxxx='...')
	   - change the namespace for this element if no prefix (xmlns='...')*/
 	for (i=0; i<nb_attributes; i++) {
		GF_XMLAttribute *att = (GF_XMLAttribute *)&attributes[i];
		if (!att->value || !strlen(att->value)) continue;
		if (!strncmp(att->name, "xmlns", 5)) {
			/* check if we have a prefix for this namespace */
			char *qname = strchr(att->name, ':');
			if (qname) {
				qname++;
			}
			/* Adds the namespace to the scene graph, either with a prefix or with NULL */
			gf_sg_add_namespace(parser->load->scene_graph, att->value, qname);

			if (!qname) {
				/* Only if there was no prefix, we change the namespace for the current element */
				parser->current_ns = gf_sg_get_namespace_code_from_name(parser->load->scene_graph, att->value);
			}
			/* Signal that when ending this element, namespaces will have to be removed */
			*has_ns = GF_TRUE;
		}
	}
}

//#define SKIP_ALL
//#define SKIP_ATTS
//#define SKIP_ATTS_PARSING
//#define SKIP_INIT

//#define SKIP_UNKNOWN_NODES

static SVG_Element *svg_parse_element(GF_SVG_Parser *parser, const char *name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes, SVG_NodeStack *parent, Bool *has_ns)
{
	GF_FieldInfo info;
	u32	tag, i, count, ns, xmlns;
	Bool needs_init, has_id;
	SVG_Element *elt = NULL;
	const char *node_name = NULL;
	const char *ev_event, *ev_observer;
	SVG_DeferredAnimation *anim = NULL;
	char *ID = NULL;

	GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[SVG Parsing] Parsing node %s\n", name));

	*has_ns = GF_FALSE;

	svg_check_namespace(parser, attributes, nb_attributes, has_ns);

	for (i=0; i<nb_attributes; i++) {
		GF_XMLAttribute *att = (GF_XMLAttribute *)&attributes[i];
		if (!att->value || !strlen(att->value)) continue;
		/* FIXME: This should be changed to reflect that xml:id has precedence over id if both are specified with different values */
		if (!stricmp(att->name, "id") || !stricmp(att->name, "xml:id")) {
			if (!ID) ID = att->value;
		}
	}

	/* CHECK: overriding the element namespace with the parent one, if given ???
	   This is wrong ??*/
	xmlns = parser->current_ns;
	if (name_space) {
		xmlns = gf_sg_get_namespace_code(parser->load->scene_graph, (char *) name_space);
		if (!xmlns) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[SVG Parsing] line %d - XMLNS prefix %s not defined - skipping\n", gf_xml_sax_get_line(parser->sax_parser), name_space));
			return NULL;
		}
	}

	/* Translates the node type (called name) from a String into a unique numeric identifier in GPAC */
	tag = xmlns ? gf_xml_get_element_tag(name, xmlns) : TAG_UndefinedNode;
	if (tag == TAG_UndefinedNode) {
#ifdef SKIP_UNKNOWN_NODES
		GF_LOG(GF_LOG_DEBUG, GF_LOG_PARSER, ("[SVG Parsing] line %d - Unknown element %s - skipping\n", gf_xml_sax_get_line(parser->sax_parser), name));
		return NULL;
#else
		tag = TAG_DOMFullNode;
#endif
	}

	/* If this element has an ID, we look in the list of elements already created in advance (in case of reference) to see if it is there,
	   in which case we will reuse it*/
	has_id = GF_FALSE;
	count = gf_list_count(parser->peeked_nodes);
	if (count && ID) {
		for (i=0; i<count; i++) {
			GF_Node *n = (GF_Node *)gf_list_get(parser->peeked_nodes, i);
			const char *n_id = gf_node_get_name(n);
			if (n_id && !strcmp(n_id, ID)) {
				gf_list_rem(parser->peeked_nodes, i);
				has_id = GF_TRUE;
				elt = (SVG_Element*)n;
				break;
			}
		}
	}

	/* If the element was found in the list of elements already created, we do not need to create it, we reuse it.
	   Otherwise, we create it based on the tag */
	if (!has_id) {
		/* Creates a node in the current scene graph */
		elt = (SVG_Element*)gf_node_new(parser->load->scene_graph, tag);
		if (!elt) {
			parser->last_error = GF_SG_UNKNOWN_NODE;
			return NULL;
		}
		/* CHECK: Why isn't this code in the gf_node_new call ?? */
		if (tag == TAG_DOMFullNode) {
			GF_DOMFullNode *d = (GF_DOMFullNode *)elt;
			d->name = gf_strdup(name);
			d->ns = xmlns;
			if (ID)
				gf_svg_parse_element_id((GF_Node *)d, ID, GF_FALSE);
		}
	}

	/* We indicate that the element is used by its parent (reference counting for safe deleting) */
	gf_node_register((GF_Node *)elt, (parent ? (GF_Node *)parent->node : NULL));
	/* We attach this element as the last child of its parent */
	if (parent && elt) gf_node_list_add_child_last( & parent->node->children, (GF_Node*)elt, & parent->last_child);

	/* By default, all elements will need initialization for rendering, except some that will explicitly set it to 0 */
	needs_init = GF_TRUE;

	if (gf_svg_is_animation_tag(tag)) {
		GF_SAFEALLOC(anim, SVG_DeferredAnimation);
		if (!anim) {
			parser->last_error = GF_OUT_OF_MEM;
			return NULL;
		}
		/*default anim target is parent node*/
		anim->animation_elt = elt;
		gf_node_register((GF_Node*)elt, NULL);
		if (!parent) {
			if (parser->command) {
				anim->target = anim->anim_parent = (SVG_Element*) parser->command->node;
			}
		} else {
			anim->target = anim->anim_parent = parent->node;
		}
	} else if (gf_svg_is_timing_tag(tag)) {
		/* warning: we use the SVG_DeferredAnimation structure for some timing nodes which are not
		   animations, but we put the parse stage at 1 (timing) see svg_parse_animation. */
		GF_SAFEALLOC(anim, SVG_DeferredAnimation);
		if (!anim) {
			parser->last_error = GF_OUT_OF_MEM;
			return NULL;
		}
		/*default anim target is parent node*/
		anim->animation_elt = elt;
		gf_node_register((GF_Node*)elt, NULL);
		if (!parent) {
			if (parser->command) {
				anim->target = anim->anim_parent = (SVG_Element*) parser->command->node;
			}
		} else {
			anim->target = anim->anim_parent = parent->node;
		}
		anim->resolve_stage = 1;
	} else if ((tag == TAG_SVG_script) || (tag==TAG_SVG_handler)) {
		/* Scripts and handlers don't render and have no initialization phase */
		needs_init = GF_FALSE;
	}

	ev_event = ev_observer = NULL;

#ifdef SKIP_ATTS
	nb_attributes = 0;
#endif

	/*set the root of the SVG tree BEFORE processing events in order to have it setup for script init (e.g. load events, including in root svg)*/
	if ((tag == TAG_SVG_svg) && !parser->has_root) {
		svg_init_root_element(parser, elt);
	}

	/*parse all att*/
	for (i=0; i<nb_attributes; i++) {
		GF_XMLAttribute *att = (GF_XMLAttribute *)&attributes[i];
		char *att_name = NULL;
		if (!att->value || !strlen(att->value)) continue;

		/* first determine in which namespace is the attribute and store the result in ns,
		then shift the char buffer to point to the local name of the attribute*/
		ns = xmlns;
		att_name = strchr(att->name, ':');
		if (att_name) {
			if (!strncmp(att->name, "xmlns", 5)) {
				ns = gf_sg_get_namespace_code(parser->load->scene_graph, att_name+1);
				att_name = att->name;
			} else {
				att_name[0] = 0;
				ns = gf_sg_get_namespace_code(parser->load->scene_graph, att->name);
				att_name[0] = ':';
				att_name++;
			}
		} else {
			att_name = att->name;
		}

		/* Begin of special cases of attributes */

		/* CHECK: Shouldn't namespaces be checked here ? */
		if (!stricmp(att_name, "style")) {
			gf_svg_parse_style((GF_Node *)elt, att->value);
			continue;
		}

		/* Some attributes of the animation elements cannot be parsed (into typed values) until the type of value is known,
		   we defer the parsing and store them temporarily as strings */
		if (anim) {
			if (!stricmp(att_name, "to")) {
				if (!anim->to)
					anim->to = gf_strdup(att->value);
				continue;
			}
			if (!stricmp(att_name, "from")) {
				if (!anim->from)
					anim->from = gf_strdup(att->value);
				continue;
			}
			if (!stricmp(att_name, "by")) {
				if (!anim->by)
					anim->by = gf_strdup(att->value);
				continue;
			}
			if (!stricmp(att_name, "values")) {
				if (!anim->values)
					anim->values = gf_strdup(att->value);
				continue;
			}
			if ((tag == TAG_SVG_animateTransform) && !stricmp(att_name, "type")) {
				if (!anim->type)
					anim->type = gf_strdup(att->value);
				continue;
			}
		}

		/* Special case for xlink:href attributes */
		if ((ns == GF_XMLNS_XLINK) && !stricmp(att_name, "href") ) {

			if (gf_svg_is_animation_tag(tag)) {
				/* For xlink:href in animation elements,
				we try to locate the target of the xlink:href to determine the type of values to be animated */
				if (anim && !anim->target_id) {
					anim->target_id = gf_strdup(att->value);
					/*The target may be NULL, if it has not yet been parsed, we will try to resolve it later on */
					anim->target = (SVG_Element *) gf_sg_find_node_by_name(parser->load->scene_graph, anim->target_id + 1);
				}
				continue;
			} else {
				/* For xlink:href attribute on elements other than animation elements,
				   we create the attribute, parse it and try to do some special process it */
				XMLRI *iri = NULL;
				if (gf_node_get_attribute_by_tag((GF_Node *)elt, TAG_XLINK_ATT_href, GF_TRUE, GF_FALSE, &info)==GF_OK) {
					gf_svg_parse_attribute((GF_Node *)elt, &info, att->value, 0);
					iri = (XMLRI *)info.far_ptr;

					/* extract streamID ref or data URL and store as file */
					svg_post_process_href(parser, (GF_Node *)elt, iri);
					continue;
				}
			}
		}

		/* For the XML Event handler element, we need to defer the parsing of some attributes */
		if ((tag == TAG_SVG_handler) && (ns == GF_XMLNS_XMLEV)) {
			if (!stricmp(att_name, "event") ) {
				ev_event = att->value;
				continue;
			}
			if (!stricmp(att_name, "observer") ) {
				ev_observer = att->value;
				continue;
			}
		}

		/*laser specific stuff*/
		if (ns == GF_XMLNS_LASER) {
			/* CHECK: we should probably check the namespace of the attribute here */
			if (!stricmp(att_name, "scale") ) {
				if (gf_node_get_attribute_by_tag((GF_Node *)elt, TAG_SVG_ATT_transform, GF_TRUE, GF_TRUE, &info)==GF_OK) {
					SVG_Point pt;
					GF_Err e;
					SVG_Transform *mat = (SVG_Transform *)info.far_ptr;
					svg_parse_point(&pt, att->value, &e);
					gf_mx2d_add_scale(&mat->mat, pt.x, pt.y);
					continue;
				}
			}
			if (!stricmp(att_name, "translation") ) {
				if (gf_node_get_attribute_by_tag((GF_Node *)elt, TAG_SVG_ATT_transform, GF_TRUE, GF_TRUE, &info)==GF_OK) {
					SVG_Point pt;
					GF_Err e;
					SVG_Transform *mat = (SVG_Transform *)info.far_ptr;
					svg_parse_point(&pt, att->value, &e);
					gf_mx2d_add_translation(&mat->mat, pt.x, pt.y);
					continue;
				}
			}
		}

		/* For all attributes of the form 'on...', like 'onclick' we create a listener for the event on the current element,
		   we connect the listener to a handler that contains the code in the 'on...' attribute. */
		/* CHECK: we should probably check the namespace of the attribute and of the element here */
		if (!strncmp(att_name, "on", 2)) {
			u32 evtType = gf_dom_event_type_by_name(att_name + 2);
			if (evtType != GF_EVENT_UNKNOWN) {
				SVG_handlerElement *handler = gf_dom_listener_build((GF_Node *) elt, evtType, 0);
				gf_dom_add_text_node((GF_Node *)handler, gf_strdup(att->value) );
				gf_node_init((GF_Node *)handler);
				continue;
			}
			svg_report(parser, GF_OK, "Skipping unknown event handler %s on node %s", att->name, name);
		}

		/* end of special cases of attributes */

		/* General attribute creation and parsing */
		if (gf_node_get_attribute_by_name((GF_Node *)elt, att_name, ns, GF_TRUE, GF_FALSE, &info)==GF_OK) {
#ifndef SKIP_ATTS_PARSING
			if (!info.name) info.name = att_name;
			GF_Err e = gf_svg_parse_attribute((GF_Node *)elt, &info, att->value, 0);
			if (e) {
				svg_report(parser, e, "Error parsing attribute %s on node %s", att->name, name);
				continue;
			}
			if (info.fieldType == SVG_ID_datatype) {

				/* in this case gf_svg_parse_attribute will have deleted the previous info.far_ptr if it existed
				 * so we need to overwrite node_name in order to avoid have a use-after-free
				 */

				node_name = *(SVG_ID *)info.far_ptr;
				/* Check if ID start with a digit, which is not a valid ID for a node according to XML (see http://www.w3.org/TR/xml/#id) */
				if (isdigit(node_name[0])) {
					svg_report(parser, GF_BAD_PARAM, "Invalid value %s for node %s %s", node_name, name, att->name);
					node_name = NULL;
				}

			} else {
				switch (info.fieldIndex) {
				case TAG_SVG_ATT_syncMaster:
				case TAG_SVG_ATT_focusHighlight:
				case TAG_SVG_ATT_initialVisibility:
				case TAG_SVG_ATT_fullscreen:
				case TAG_SVG_ATT_requiredFonts:
					/*switch LASeR Configuration to v2 because these attributes are not part of v1*/
					svg_lsr_set_v2(parser);
					break;
				}
			}
#endif
			continue;
		}

		/* all other attributes (??? failed to be created) should fall in this category */
		svg_report(parser, GF_OK, "Skipping attribute %s on node %s", att->name, name);
	}

	/* When a handler element specifies the event attribute, an implicit listener is defined */
	if (ev_event) {
		GF_Node *node = (GF_Node *)elt;
		SVG_Element *listener;
		u32 type;
		listener = (SVG_Element *) gf_node_new(node->sgprivate->scenegraph, TAG_SVG_listener);
		/*We don't want to insert the implicit listener in the DOM. However remember
		the listener at the handler level in case the handler gets destroyed*/
		gf_node_set_private(node, (GF_Node*)listener );
		gf_node_register((GF_Node*)listener, NULL);

		/* this listener listens to the given type of event */
		type = gf_dom_event_type_by_name(ev_event);
		gf_node_get_attribute_by_tag(node, TAG_XMLEV_ATT_event, GF_TRUE, GF_FALSE, &info);
		((XMLEV_Event *)info.far_ptr)->type = type;
		gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_event, GF_TRUE, GF_FALSE, &info);
		((XMLEV_Event *)info.far_ptr)->type = type;
		gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_handler, GF_TRUE, GF_FALSE, &info);
		((XMLRI *)info.far_ptr)->target = node;

		if (ev_observer) {
			/* An observer was specified, so it needs to be used */
			gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_observer, GF_TRUE, GF_FALSE, &info);
			gf_svg_parse_attribute((GF_Node *)elt, &info, (char*)ev_observer, 0);
		} else {
			/* No observer specified, this listener listens with the parent of the handler as the event target */
			gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_target, GF_TRUE, GF_FALSE, &info);
			((XMLRI *)info.far_ptr)->target = parent->node;
		}
		/* if the target was found (already parsed), we are fine, otherwise we need to try to find it again,
		   we place the listener in the deferred listener list */
		if ( ((XMLRI *)info.far_ptr)->target)
			gf_node_dom_listener_add(((XMLRI *)info.far_ptr)->target, (GF_Node *) listener);
		else
			gf_list_add(parser->deferred_listeners, listener);
	}

	if (!node_name && ID)
		node_name = ID;

	/* if the new element has an id, we try to resolve deferred references (including animations, href and listeners (just above)*/
	if (node_name) {
		if (!has_id) {
			/* if the element was already created before this call, we don't need to get a numerical id, we have it already */
			gf_svg_parse_element_id((GF_Node *)elt, node_name, parser->command_depth ? GF_TRUE : GF_FALSE);
		}
		svg_resolved_refs(parser, parser->load->scene_graph, node_name);
	}

	/* if the new element is an animation, now that all specified attributes have been found,
	   we can start parsing them */
	if (anim) {
		/*FIXME - we need to parse from/to/values but not initialize the stack*/
//		if (parser->load->flags & GF_SM_LOAD_FOR_PLAYBACK) {
		needs_init = GF_FALSE;
		if (svg_parse_animation(parser, parser->load->scene_graph, anim, NULL, 0)) {
			svg_delete_deferred_anim(anim, NULL);
		} else {
			gf_list_add(parser->deferred_animations, anim);
		}
//		} else {
//			svg_delete_deferred_anim(anim, NULL);
//		}
	}

#ifndef SKIP_INIT
	if (needs_init) {
		/* For elements that need it, we initialize the rendering stack */
		gf_node_init((GF_Node *)elt);
	}
#endif

	if (parent && elt) {
		/*mark parent element as dirty (new child added) and invalidate parent graph for progressive rendering*/
		gf_node_dirty_set((GF_Node *)parent->node, GF_SG_CHILD_DIRTY, GF_TRUE);
		/*request scene redraw*/
		if (parser->load->scene_graph->NodeCallback) {
			parser->load->scene_graph->NodeCallback(parser->load->scene_graph->userpriv, GF_SG_CALLBACK_MODIFIED, NULL, NULL);
		}
	}

	/*If we are in playback mode, we register (reference counting for safe deleting) the listener element with the element that uses it */
	if ((parser->load->flags & GF_SM_LOAD_FOR_PLAYBACK) && elt && (tag==TAG_SVG_listener)) {
		Bool post_pone = GF_FALSE;
		SVG_Element *par = NULL;
		SVG_Element *listener = (SVG_Element *)elt;

		if (gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_observer, GF_FALSE, GF_FALSE, &info) == GF_OK) {
			XMLRI *observer = (XMLRI *)info.far_ptr;
			if (observer->type == XMLRI_ELEMENTID) {
				if (!observer->target) post_pone = GF_TRUE;
				else par = (SVG_Element *)observer->target;
			}
		}

		if (gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_target, GF_FALSE, GF_FALSE, &info) == GF_OK) {
			XMLRI *target = (XMLRI *)info.far_ptr;
			if (!par && (target->type == XMLRI_ELEMENTID)) {
				if (!target->target) post_pone = GF_TRUE;
				else par = (SVG_Element *)target->target;
			}
		}
		/*check handler, create it if not specified*/
		if (parent && (gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_handler, GF_TRUE, GF_FALSE, &info) == GF_OK)) {
			XMLRI *handler = (XMLRI *)info.far_ptr;
			if (!handler->target) {
				if (!handler->string) handler->target = parent->node;
			}
		}
		/*if event is a key event, register it with root*/
		if (!par && gf_node_get_attribute_by_tag((GF_Node *)listener, TAG_XMLEV_ATT_event, GF_FALSE, GF_FALSE, &info) == GF_OK) {
			XMLEV_Event *ev = (XMLEV_Event *)info.far_ptr;
			if ((ev->type >= GF_EVENT_KEYUP) && (ev->type <= GF_EVENT_TEXTINPUT)) par = (SVG_Element*) listener->sgprivate->scenegraph->RootNode;
		}

		if (post_pone) {
			gf_list_add(parser->deferred_listeners, listener);
		} else {
			if (!par && parent) par = parent->node;
			gf_node_dom_listener_add((GF_Node *)par, (GF_Node *) listener);
		}
	}
	return elt;
}


static GF_Err lsr_parse_command(GF_SVG_Parser *parser, const GF_XMLAttribute *attributes, u32 nb_attributes)
{
	GF_FieldInfo info;
	GF_Node *opNode;
	Bool is_replace = GF_FALSE;
	char *atNode = NULL;
	char *atAtt = NULL;
	char *atOperandNode = NULL;
	char *atOperandAtt = NULL;
	char *atValue = NULL;
	char *atPoint = NULL;
	char *atInteger = NULL;
	char *atEvent = NULL;
	char *atString = NULL;
	GF_CommandField *field;
	s32 index = -1;
	u32 i;
	switch (parser->command->tag) {
	case GF_SG_LSR_NEW_SCENE:
		return GF_OK;
	case GF_SG_LSR_DELETE:
		for (i=0; i<nb_attributes; i++) {
			GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
			if (!strcmp(att->name, "ref")) atNode = att->value;
			else if (!strcmp(att->name, "attributeName")) atAtt = att->value;
			else if (!strcmp(att->name, "index")) index = atoi(att->value);
		}
		if (!atNode) return svg_report(parser, GF_BAD_PARAM, "Missing node ref for command");
		/*should be a XML IDREF, not an XML IRI*/
		if (atNode[0]=='#') atNode++;

		parser->command->node = svg_find_node(parser, atNode);
		if (!parser->command->node) return svg_report(parser, GF_BAD_PARAM, "Cannot find node node ref %s for command", atNode);
		if (atAtt) {
			if (!strcmp(atAtt, "children")) {
				field = gf_sg_command_field_new(parser->command);
				field->pos = index;
				field->fieldIndex = 0;
				field->fieldType = 0;
			} else {
				if (gf_node_get_attribute_by_name(parser->command->node, atAtt, parser->current_ns, GF_FALSE, GF_FALSE, &info)==GF_OK) {
					field = gf_sg_command_field_new(parser->command);
					field->pos = index;
					field->fieldIndex = info.fieldIndex;
					field->fieldType = info.fieldType;
				}
			}
		} else if (index>=0) {
			field = gf_sg_command_field_new(parser->command);
			field->pos = index;
		}
		gf_node_register(parser->command->node, NULL);
		return GF_OK;
	case GF_SG_LSR_REPLACE:
		is_replace = GF_TRUE;
	case GF_SG_LSR_ADD:
	case GF_SG_LSR_INSERT:
		for (i=0; i<nb_attributes; i++) {
			GF_XMLAttribute *att = (GF_XMLAttribute *)&attributes[i];
			if (!strcmp(att->name, "ref")) atNode = att->value;
			else if (!strcmp(att->name, "operandElementId")) atOperandNode = att->value;
			else if (!strcmp(att->name, "operandAttributeName")) atOperandAtt = att->value;
			else if (!strcmp(att->name, "value")) atValue = att->value;
			else if (!strcmp(att->name, "attributeName")) atAtt = att->value;
			/*replace only*/
			else if (!strcmp(att->name, "index")) index = atoi(att->value);
		}
		if (!atNode) return svg_report(parser, GF_BAD_PARAM, "Missing node ref for command");
		parser->command->node = svg_find_node(parser, atNode);
		if (!parser->command->node) return svg_report(parser, GF_BAD_PARAM, "Cannot find node node ref %s for command", atNode);
		/*child or node replacement*/
		if ( (is_replace || (parser->command->tag==GF_SG_LSR_INSERT)) && (!atAtt || !strcmp(atAtt, "children")) ) {
			field = gf_sg_command_field_new(parser->command);
			field->pos = index;
			if (atAtt) field->fieldIndex = TAG_LSR_ATT_children;
			gf_node_register(parser->command->node, NULL);
			return GF_OK;
		}
		if (!atAtt) return svg_report(parser, GF_BAD_PARAM, "Missing attribute name for command");
		if (!strcmp(atAtt, "textContent")) {
			field = gf_sg_command_field_new(parser->command);
			field->pos = -1;
			field->fieldIndex = (u32) -1;
			field->fieldType = DOM_String_datatype;
			gf_node_register(parser->command->node, NULL);
			if (atValue) {
				field->field_ptr = gf_svg_create_attribute_value(field->fieldType);
				*(SVG_String *)field->field_ptr = gf_strdup(atValue);
			}
			return GF_OK;
		}
		if (!strcmp(atAtt, "scale") || !strcmp(atAtt, "translation") || !strcmp(atAtt, "rotation")) {
			if (!strcmp(atAtt, "scale")) {
				info.fieldType = SVG_Transform_Scale_datatype;
				info.fieldIndex = TAG_LSR_ATT_scale;
			}
			else if (!strcmp(atAtt, "translation")) {
				info.fieldType = SVG_Transform_Translate_datatype;
				info.fieldIndex = TAG_LSR_ATT_translation;
			}
			else /*if (!strcmp(atAtt, "rotation")) */ {
				info.fieldType = SVG_Transform_Rotate_datatype;
				info.fieldIndex = TAG_LSR_ATT_rotation;
			}
		} else {
			/*FIXME - handle namespace properly here !!*/
			info.fieldIndex = gf_xml_get_attribute_tag(parser->command->node, atAtt, (strchr(atAtt, ':')==NULL) ? parser->current_ns : 0);
			info.fieldType = gf_xml_get_attribute_type(info.fieldIndex);

#ifndef GPAC_DISABLE_LASER
			/*
						if (gf_lsr_anim_type_from_attribute(info.fieldIndex)<0) {
							return svg_report(parser, GF_BAD_PARAM, "Attribute %s of element %s is not updatable\n", atAtt, gf_node_get_class_name(parser->command->node));
						}
			*/
#endif /*GPAC_DISABLE_LASER*/
		}

		opNode = NULL;
		if (atOperandNode) {
			opNode = gf_sg_find_node_by_name(parser->load->scene_graph, atOperandNode);
			if (!opNode) return svg_report(parser, GF_BAD_PARAM, "Cannot find operand element %s for command", atOperandNode);
		}
		if (!atValue && (!atOperandNode || !atOperandAtt) ) return svg_report(parser, GF_BAD_PARAM, "Missing attribute value for command");
		field = gf_sg_command_field_new(parser->command);
		field->pos = index;
		field->fieldIndex = info.fieldIndex;
		field->fieldType = info.fieldType;
		if (atValue) {
			GF_FieldInfo nf;
			nf.fieldType = info.fieldType;
			field->field_ptr = nf.far_ptr = gf_svg_create_attribute_value(info.fieldType);
			if (field->field_ptr) {
				gf_svg_parse_attribute(parser->command->node, &nf, atValue, (u8) info.fieldType);
				if (info.fieldType==XMLRI_datatype)
					svg_process_media_href(parser, parser->command->node, (XMLRI*)field->field_ptr);
			}
		} else if (opNode) {
			parser->command->fromNodeID = gf_node_get_id(opNode);
			/*FIXME - handle namespace properly here !!*/
			parser->command->fromFieldIndex = gf_xml_get_attribute_tag(opNode, atOperandAtt, parser->current_ns);
		}
		gf_node_register(parser->command->node, NULL);
		return GF_OK;
	case GF_SG_LSR_ACTIVATE:
	case GF_SG_LSR_DEACTIVATE:
		for (i=0; i<nb_attributes; i++) {
			GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
			if (!strcmp(att->name, "ref")) atNode = att->value;
		}
		if (!atNode) return svg_report(parser, GF_BAD_PARAM, "Missing node ref for command");
		/*should be a XML IDREF, not an XML IRI*/
		if (atNode[0]=='#') atNode++;
		parser->command->node = svg_find_node(parser, atNode);
		if (!parser->command->node) return svg_report(parser, GF_BAD_PARAM, "Cannot find node ref %s for command", atNode);
		gf_node_register(parser->command->node, NULL);

		/*switch to V2*/
		svg_lsr_set_v2(parser);

		return GF_OK;
	case GF_SG_LSR_SEND_EVENT:
		for (i=0; i<nb_attributes; i++) {
			GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
			if (!strcmp(att->name, "ref")) atNode = att->value;
			else if (!strcmp(att->name, "event")) atEvent = att->value;
			else if (!strcmp(att->name, "pointvalue")) atPoint = att->value;
			else if (!strcmp(att->name, "intvalue")) atInteger = att->value;
			else if (!strcmp(att->name, "stringvalue")) atString = att->value;
		}

		if (!atEvent) return svg_report(parser, GF_BAD_PARAM, "Missing event name for command");
		if (!atNode) return svg_report(parser, GF_BAD_PARAM, "Missing node ref for command");
		/*should be a XML IDREF, not an XML IRI*/
		if (atNode[0]=='#') atNode++;
		parser->command->node = svg_find_node(parser, atNode);
		if (!parser->command->node) return svg_report(parser, GF_BAD_PARAM, "Cannot find node node ref %s for command", atNode);
		gf_node_register(parser->command->node, NULL);

		parser->command->send_event_name = gf_dom_event_type_by_name(atEvent);

		parser->command->send_event_integer = 0;
		if (atString) {
			u32 key = gf_dom_get_key_type(atString);
			if (key == GF_KEY_UNIDENTIFIED) {
				parser->command->send_event_string = gf_strdup(atString);
			} else {
				parser->command->send_event_integer = key;
			}
		}
		if (atInteger) parser->command->send_event_integer = atoi(atInteger);
		if (atPoint) {
			SVG_Point pt;
			GF_Err e;
			svg_parse_point(&pt, atPoint, &e);
			parser->command->send_event_x = FIX2INT(pt.x);
			parser->command->send_event_y = FIX2INT(pt.y);
		}
		return GF_OK;
	default:
		return GF_NOT_SUPPORTED;
	}
}


static u32 lsr_get_command_by_name(const char *name)
{
	if (!strcmp(name, "NewScene")) return GF_SG_LSR_NEW_SCENE;
	else if (!strcmp(name, "RefreshScene")) return GF_SG_LSR_REFRESH_SCENE;
	else if (!strcmp(name, "Add")) return GF_SG_LSR_ADD;
	else if (!strcmp(name, "Replace")) return GF_SG_LSR_REPLACE;
	else if (!strcmp(name, "Delete")) return GF_SG_LSR_DELETE;
	else if (!strcmp(name, "Insert")) return GF_SG_LSR_INSERT;
	else if (!strcmp(name, "Restore")) return GF_SG_LSR_RESTORE;
	else if (!strcmp(name, "Save")) return GF_SG_LSR_SAVE;
	else if (!strcmp(name, "Clean")) return GF_SG_LSR_CLEAN;
	else if (!strcmp(name, "SendEvent")) return GF_SG_LSR_SEND_EVENT;
	else if (!strcmp(name, "Activate")) return GF_SG_LSR_ACTIVATE;
	else if (!strcmp(name, "Deactivate")) return GF_SG_LSR_DEACTIVATE;
	return GF_SG_UNDEFINED;
}

static GF_ESD *lsr_parse_header(GF_SVG_Parser *parser, const char *name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes)
{
	u32 i;
	if (!strcmp(name, "LASeRHeader")) {
		GF_ESD *esd;
		GF_LASERConfig *lsrc = (GF_LASERConfig *) gf_odf_desc_new(GF_ODF_LASER_CFG_TAG);
		for (i=0; i<nb_attributes; i++) {
			GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
			if (!strcmp(att->name, "profile")) lsrc->profile = !strcmp(att->value, "full") ? 1 : 0;
			else if (!strcmp(att->name, "level")) lsrc->level = atoi(att->value);
			else if (!strcmp(att->name, "resolution")) lsrc->resolution = atoi(att->value);
			else if (!strcmp(att->name, "timeResolution")) lsrc->time_resolution = atoi(att->value);
			else if (!strcmp(att->name, "coordBits")) lsrc->coord_bits = atoi(att->value);
			else if (!strcmp(att->name, "scaleBits_minus_coordBits")) lsrc->scale_bits_minus_coord_bits = atoi(att->value);
			else if (!strcmp(att->name, "colorComponentBits")) lsrc->colorComponentBits = atoi(att->value);
			else if (!strcmp(att->name, "newSceneIndicator")) lsrc->newSceneIndicator = (!strcmp(att->value, "yes") || !strcmp(att->value, "true")) ? 1 : 0;
			else if (!strcmp(att->name, "useFullRequestHost")) lsrc->fullRequestHost = (!strcmp(att->value, "yes") || !strcmp(att->value, "true")) ? 1 : 0;
			else if (!strcmp(att->name, "pathComponents")) lsrc->fullRequestHost = atoi(att->value);
			else if (!strcmp(att->name, "extensionIDBits")) lsrc->extensionIDBits = atoi(att->value);
			/*others are ignored in GPAC atm*/
		}
		esd = gf_odf_desc_esd_new(2);
		gf_odf_desc_del((GF_Descriptor *)esd->decoderConfig->decoderSpecificInfo);
		esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) lsrc;
		esd->decoderConfig->streamType = GF_STREAM_SCENE;
		esd->decoderConfig->objectTypeIndication = GF_CODECID_LASER;
		esd->slConfig->timestampResolution = lsrc->time_resolution ? lsrc->time_resolution : 1000;
		return esd;
	}
	return NULL;
}


static void svg_node_start(void *sax_cbck, const char *name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes)
{
#ifndef SKIP_ALL
	GF_SVG_Parser *parser = (GF_SVG_Parser *)sax_cbck;
	SVG_NodeStack *stack, *parent;
	SVG_Element *elt;
	SVG_Element *cond = NULL;
	u32 xmlns;
	Bool has_ns;

	parent = (SVG_NodeStack *)gf_list_last(parser->node_stack);

	/*switch to conditional*/
	if (parent && parent->node->sgprivate->tag==TAG_LSR_conditional) {
		cond = parent->node;
		parent = NULL;
	}

	/* If the loader was created with the DIMS type and this is the root element, restore the stream and AU
	context - in DIMS? we only have one stream an dcommands are stacked in the last AU of the stream*/
	if (!parent && (parser->load->type == GF_SM_LOAD_DIMS) && parser->load->ctx) {

		/*if not created, do it now*/
		if (!gf_list_count(parser->load->ctx->streams)) {
			parser->laser_es = gf_sm_stream_new(parser->load->ctx, 1, GF_STREAM_SCENE, GF_CODECID_DIMS);
			if (!parser->laser_es) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}
			parser->laser_es->timeScale = 1000;
			/* Create a default AU to behave as other streams (LASeR, BIFS)
			   but it is left empty, there is no notion of REPLACE Scene or NEw Scene,
			   the RAP is the graph */
			parser->laser_au = gf_sm_stream_au_new(parser->laser_es, 0, 0, GF_TRUE);
			if (!parser->laser_au) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}
		} else {
			parser->laser_es = gf_list_get(parser->load->ctx->streams, 0);
			parser->laser_au = gf_list_last(parser->laser_es->AUs);
		}
	}

	/*saf setup*/
	if ((!parent && (parser->load->type!=GF_SM_LOAD_SVG)) || cond) {
		u32 com_type;

		has_ns=GF_FALSE;
		svg_check_namespace(parser, attributes, nb_attributes, &has_ns);

		/*nothing to do, the context is already created*/
		if (!strcmp(name, "SAFSession")) return;
		/*nothing to do, wait for the laser (or other) header before creating stream)*/
		if (!strcmp(name, "sceneHeader")) return;
		/*nothing to do, wait for the laser (or other) header before creating stream)*/
		if (!strcmp(name, "LASeRHeader")) {
			GF_ESD *esd;

			if (!parser->load->ctx) {
				svg_report(parser, GF_BAD_PARAM, "Invalid parser context");
				return;
			}
			esd = lsr_parse_header(parser, name, name_space, attributes, nb_attributes);
			if (!esd) {
				svg_report(parser, GF_BAD_PARAM, "Invalid LASER Header");
				return;
			}
			/*TODO find a better way of assigning an ID to the laser stream...*/
			esd->ESID = 1;
			parser->laser_es = gf_sm_stream_new(parser->load->ctx, esd->ESID, esd->decoderConfig->streamType, esd->decoderConfig->objectTypeIndication);
			if (!parser->laser_es) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}
			if (!parser->load->ctx->root_od) {
				parser->load->ctx->root_od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_IOD_TAG);
				if (!parser->load->ctx->root_od) {
					svg_report(parser, GF_OUT_OF_MEM, NULL);
					return;
				}
			}
			gf_list_add(parser->load->ctx->root_od->ESDescriptors, esd);
			parser->laser_es->timeScale = esd->slConfig->timestampResolution;
			return;
		}
		if (!strcmp(name, "sceneUnit") ) {
			u32 time, i;
			Bool rap;
			time = 0;
			rap =  GF_FALSE;
			if (!parser->laser_es) {
				svg_report(parser, GF_BAD_PARAM, "No LASER stream specified");
				return;
			}
			if (!gf_list_count(parser->laser_es->AUs)) rap = GF_TRUE;
			for (i=0; i<nb_attributes; i++) {
				GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
				if (!strcmp(att->name, "time")) time = atoi(att->value);
				else if (!strcmp(att->name, "rap")) rap = !strcmp(att->value, "yes") ? GF_TRUE : GF_FALSE;
			}
			/*create new laser unit*/
			parser->laser_au = gf_sm_stream_au_new(parser->laser_es, time, 0, rap);
			if (!parser->laser_au) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}
			return;
		}

		if (!strcmp(name, "StreamHeader") || !strcmp(name, "RemoteStreamHeader")
		        /*SAF & SAFML are just a pain ...*/
		        || !strcmp(name, "mediaHeader") || !strcmp(name, "imageHeader")
		   ) {
			char *url = NULL;
			char *src = NULL;
			const char *ID = NULL;
			u32 time, codecid, ST, i, ts_res;
			GF_ODUpdate *odU;
			GF_ObjectDescriptor *od;
			SVG_SAFExternalStream*st;
			/*create a SAF stream*/
			if (!parser->saf_es && parser->load->ctx) {
				GF_ESD *esd = (GF_ESD*)gf_odf_desc_esd_new(2);
				esd->ESID = 0xFFFE;
				esd->decoderConfig->streamType = GF_STREAM_OD;
				esd->decoderConfig->objectTypeIndication = 1;
				parser->saf_es = gf_sm_stream_new(parser->load->ctx, esd->ESID, GF_STREAM_OD, GF_CODECID_OD_V1);
				if (!parser->saf_es) {
					svg_report(parser, GF_OUT_OF_MEM, NULL);
					return;
				}
				if (!parser->load->ctx->root_od) {
					parser->load->ctx->root_od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_IOD_TAG);
					if (!parser->load->ctx->root_od) {
						svg_report(parser, GF_OUT_OF_MEM, NULL);
						return;
					}
				}
				parser->saf_es->timeScale = 1000;
				gf_list_add(parser->load->ctx->root_od->ESDescriptors, esd);
			}
			time = 0;
			ts_res = 1000;
			codecid = ST = 0;
			for (i=0; i<nb_attributes; i++) {
				GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
				if (!strcmp(att->name, "time")) time = atoi(att->value);
				else if (!strcmp(att->name, "rap")) ;//rap = !strcmp(att->value, "yes") ? 1 : 0;
				else if (!strcmp(att->name, "url")) url = gf_strdup(att->value);
				else if (!strcmp(att->name, "streamID")) ID = att->value;
				else if (!strcmp(att->name, "objectTypeIndication")) codecid = atoi(att->value);
				else if (!strcmp(att->name, "codecID")) {
					codecid = GF_4CC(att->value[0],att->value[1],att->value[2],att->value[3]);
				}
				else if (!strcmp(att->name, "streamType")) ST = atoi(att->value);
				else if (!strcmp(att->name, "timeStampResolution")) ts_res = atoi(att->value);
				else if (!strcmp(att->name, "source")) src = att->value;

			}
			if (!strcmp(name, "imageHeader")) ST = 4;

			/*create new SAF stream*/
			st = svg_saf_get_next_available_stream(parser);
			st->stream_name = ID ? gf_strdup(ID) : NULL;

			/*create new SAF unit*/
			parser->saf_au = gf_sm_stream_au_new(parser->saf_es, time, 0, GF_FALSE);
			if (!parser->saf_au) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}

			odU = (GF_ODUpdate *) gf_odf_com_new(GF_ODF_OD_UPDATE_TAG);
			if (!odU) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}
			gf_list_add(parser->saf_au->commands, odU);
			od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG);
			if (!od) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}
			gf_list_add(odU->objectDescriptors, od);
			od->objectDescriptorID = st->id;

			if (url) {
				od->URLString = url;
			} else {
				GF_MuxInfo *mux;
				GF_ESD *esd = (GF_ESD*)gf_odf_desc_esd_new(2);
				if (!esd) {
					svg_report(parser, GF_OUT_OF_MEM, NULL);
					return;
				}
				gf_list_add(od->ESDescriptors, esd);
				esd->decoderConfig->objectTypeIndication = codecid;
				esd->decoderConfig->streamType = ST;
				esd->ESID = st->id;

				mux = (GF_MuxInfo *)gf_odf_desc_new(GF_ODF_MUXINFO_TAG);
				if (!mux) {
					svg_report(parser, GF_OUT_OF_MEM, NULL);
					return;
				}
				gf_list_add(esd->extensionDescriptors, mux);

				/*global source for stream, don't use nhml dumping*/
				if (src) {
					mux->file_name = gf_strdup(src);
					st->nhml_info = NULL;
				} else {
					FILE *nhml;
					char szName[1024];
					if (parser->load->localPath) {
						strcpy(szName, parser->load->localPath);
						strcat(szName, "/");
						strcat(szName, ID ? ID : "");
					} else {
						strcpy(szName, ID ? ID : "");
					}
					strcat(szName, "_temp.nhml");
					mux->file_name = gf_strdup(szName);
					st->nhml_info = mux->file_name;
					nhml = gf_fopen(st->nhml_info, "wt");
					if (nhml) {
						gf_fprintf(nhml, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
						gf_fprintf(nhml, "<NHNTStream version=\"1.0\" timeScale=\"%d\" streamType=\"%d\" objectTypeIndication=\"%d\" inRootOD=\"no\" trackID=\"%d\">\n", ts_res, ST, codecid, st->id);
						gf_fclose(nhml);
					} else {
						GF_LOG(GF_LOG_WARNING, GF_LOG_PARSER, ("[LASeR Parser] Error opening nhml file %s while preparing import\n", st->nhml_info));
					}
					mux->delete_file = GF_TRUE;
				}
			}
			return;
		}
		if (!strcmp(name, "mediaUnit") || !strcmp(name, "imageUnit") ) {
			FILE *nhml;
			char *id = NULL;
			char *src = NULL;
			SVG_SAFExternalStream*st;
			u32 i, rap, time, offset, length;
			rap = time = offset = length = 0;
			for (i=0; i<nb_attributes; i++) {
				GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
				if (!strcmp(att->name, "time")) time = atoi(att->value);
				else if (!strcmp(att->name, "source")) src = att->value;
				else if (!strcmp(att->name, "ref")) id = att->value;
				else if (!strcmp(att->name, "rap")) rap = !strcmp(att->value, "yes") ? 1 : 0;
				else if (!strcmp(att->name, "startOffset")) offset = atoi(att->value);
				else if (!strcmp(att->name, "length")) length = atoi(att->value);
			}
			st = svg_saf_get_stream(parser, 0, id);
			if (!st || !st->nhml_info) {
				return;
			}
			nhml = gf_fopen(st->nhml_info, "a+t");
			gf_fprintf(nhml, "<NHNTSample ");
			if (time) gf_fprintf(nhml, "DTS=\"%d\" ", time);
			if (length) gf_fprintf(nhml, "dataLength=\"%d\" ", length);
			if (offset) gf_fprintf(nhml, "mediaOffset=\"%d\" ", offset);
			if (rap) gf_fprintf(nhml, "isRAP=\"yes\" ");
			if (src) gf_fprintf(nhml, "mediaFile=\"%s\" ", src);
			gf_fprintf(nhml, "/>\n");
			gf_fclose(nhml);
			return;
		}
		if (!strcmp(name, "endOfStream") ) {
			FILE *nhml;
			char *id = NULL;
			u32 i;
			SVG_SAFExternalStream*st;
			for (i=0; i<nb_attributes; i++) {
				GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i];
				if (!strcmp(att->name, "ref")) id = att->value;
			}
			st = svg_saf_get_stream(parser, 0, id);
			if (!st || !st->nhml_info) {
				return;
			}
			nhml = gf_fopen(st->nhml_info, "a+t");
			gf_fprintf(nhml, "</NHNTStream>\n");
			gf_fclose(nhml);
			return;
		}

		if (!strcmp(name, "endOfSAFSession") ) {
			return;
		}

		if ((parser->load->type==GF_SM_LOAD_XSR) && !parser->laser_au && !cond) {
			if (parser->load->flags & GF_SM_LOAD_CONTEXT_READY) {
				if (!parser->laser_es) {
					svg_report(parser, GF_NON_COMPLIANT_BITSTREAM, NULL);
					return;
				}
				parser->laser_au = gf_sm_stream_au_new(parser->laser_es, 0, 0, GF_FALSE);
				if (!parser->laser_au) {
					svg_report(parser, GF_OUT_OF_MEM, NULL);
					return;
				}
			} else {
				svg_report(parser, GF_BAD_PARAM, "LASeR sceneUnit not defined for command %s", name);
				return;
			}
		}
		/*command parsing*/
		com_type = lsr_get_command_by_name(name);
		if (com_type != GF_SG_UNDEFINED) {
			SVG_NodeStack *top;
			GF_Err e;
			parser->command = gf_sg_command_new(parser->load->scene_graph, com_type);
			if (!parser->command) {
				svg_report(parser, GF_OUT_OF_MEM, NULL);
				return;
			}

			/*this is likely a conditional start - update unknown depth level*/
			top = (SVG_NodeStack*)gf_list_last(parser->node_stack);
			if (top) {
				top->unknown_depth ++;
				parser->command_depth++;
			}

			e = lsr_parse_command(parser, attributes, nb_attributes);
			if (e!= GF_OK) {
				parser->command->node = NULL;
				gf_sg_command_del(parser->command);
				parser->command = NULL;
				GF_LOG(GF_LOG_ERROR, GF_LOG_PARSER, ("[LASeR Parser] Error parsing %s command - skipping\n", (parser->load->type==GF_SM_LOAD_XSR) ? "LASeR" : "DIMS"));
				return;
			}

			if (cond) {
				GF_DOMUpdates *up = cond->children ? (GF_DOMUpdates *)cond->children->node : NULL;
				if (!up) {
					up = gf_dom_add_updates_node((GF_Node*)cond);

					if (parser->load->flags & GF_SM_LOAD_FOR_PLAYBACK) {
						gf_node_set_callback_function((GF_Node*)up, xsr_exec_command_list);
					}
				}
				gf_list_add(up->updates, parser->command);
			} else if (parser->laser_au) {
				gf_list_add(parser->laser_au->commands, parser->command);
			}
			switch (com_type) {
			case GF_SG_LSR_NEW_SCENE:
			case GF_SG_LSR_REFRESH_SCENE:
				if (parser->laser_au) parser->laser_au->flags |= GF_SM_AU_RAP;
				break;
			}
			parser->current_ns = GF_XMLNS_SVG;
			return;
		}
	}

	if (!parent && !parser->command && (parser->load->flags & GF_SM_LOAD_CONTEXT_STREAMING)) {
		gf_sg_reset(parser->load->scene_graph);
		parser->has_root = 0;
	}

	/*something not supported happened (bad command name, bad root, ...) */
	if ((parser->has_root==1) && !parent && !parser->command)
		return;

	xmlns = parser->current_ns;
	has_ns = GF_FALSE;

	elt = svg_parse_element(parser, name, name_space, attributes, nb_attributes, parent, &has_ns);
	if (!elt) {
		if (parent) parent->unknown_depth++;
		else if (cond) parser->command_depth++;
		return;
	}
	GF_SAFEALLOC(stack, SVG_NodeStack);
	if (!stack) {
		parser->last_error = GF_OUT_OF_MEM;
		return;
	}
	stack->node = elt;
	stack->current_ns = xmlns;
	stack->has_ns = has_ns;
	gf_list_add(parser->node_stack, stack);

	if ( (gf_node_get_tag((GF_Node *)elt) == TAG_SVG_svg) &&
	        (!parser->has_root || (parser->command && !parser->command->node) )
	   ) {

		if (!parser->has_root) svg_init_root_element(parser, elt);
		if (parser->command) parser->command->node = (GF_Node*)elt;
	} else if (!parent && parser->has_root && parser->command) {
		GF_CommandField *field = (GF_CommandField *)gf_list_get(parser->command->command_fields, 0);
		if (field) {
			/*either not assigned or textContent*/
			if (field->new_node && (field->new_node->sgprivate->tag==TAG_DOMText)) {
				gf_node_unregister(field->new_node, NULL);
				field->new_node = NULL;
			}
			if (field->new_node) {
				field->field_ptr = &field->node_list;
				gf_node_list_add_child(& field->node_list, field->new_node);
				field->new_node = NULL;
				gf_node_list_add_child( & field->node_list, (GF_Node*) elt);
			} else if (field->node_list) {
				gf_node_list_add_child(& field->node_list, (GF_Node*) elt);
			} else {
				field->new_node = (GF_Node*)elt;
				field->field_ptr = &field->new_node;
			}
		} else {
			if ((parser->command->tag!=GF_SG_LSR_NEW_SCENE) || (gf_node_get_tag((GF_Node *)elt) != TAG_SVG_svg)) {
				gf_list_del_item(parser->node_stack, stack);
				gf_free(stack);
				gf_node_unregister((GF_Node *)elt, NULL);
				return;
			}
			if(!parser->command->node)
				parser->command->node = (GF_Node *)elt;
		}
	} else if (!parser->has_root ) {
		gf_list_del_item(parser->node_stack, stack);
		gf_free(stack);
		gf_node_unregister((GF_Node *)elt, NULL);
	} else if ((parser->has_root==2) && !parser->fragment_root) {
		parser->fragment_root = (GF_Node *)elt;
	}
#endif
}

static void svg_node_end(void *sax_cbck, const char *name, const char *name_space)
{
#ifndef SKIP_ALL
#ifdef SKIP_UNKNOWN_NODES
	u32 ns;
#endif
	GF_SVG_Parser *parser = (GF_SVG_Parser *)sax_cbck;
	SVG_NodeStack *top = (SVG_NodeStack *)gf_list_last(parser->node_stack);

	if (!top) {
		if (parser->laser_au && !strcmp(name, "sceneUnit")) {
			parser->laser_au = NULL;
			return;
		}
		if (parser->command) {
			u32 com_type = lsr_get_command_by_name(name);
			if (com_type == parser->command->tag) {
				if (parser->load->type==GF_SM_LOAD_DIMS && parser->load->flags & GF_SM_LOAD_FOR_PLAYBACK) {
					gf_sg_command_apply(parser->load->scene_graph, parser->command, 0);
					gf_sg_command_del(parser->command);
				}
				parser->command = NULL;
				return;
			}
		}
		/*error*/
		return;
	}

#ifdef SKIP_UNKNOWN_NODES
	ns = parser->current_ns;
	if (name_space)
		ns = gf_sg_get_namespace_code(parser->load->scene_graph, (char *) name_space);

	/*only remove created nodes ... */
	if (gf_xml_get_element_tag(name, ns) != TAG_UndefinedNode)
#endif
	{
		const char *the_name;
		Bool mismatch = GF_FALSE;
		SVG_Element *node = top->node;
		/*check node name...*/
		the_name = gf_node_get_class_name((GF_Node *)node);
		if (name_space && strstr(the_name, name_space) && strstr(the_name, name) ) {}
		else if (!strcmp(the_name, name) ) {}
		else mismatch = GF_TRUE;

		if (mismatch) {
			if (top->unknown_depth) {
				top->unknown_depth--;
				return;
			} else {
				svg_report(parser, GF_BAD_PARAM, "SVG depth mismatch: expecting </%s> got </%s>", the_name, name);
				return;
			}
		}
		parser->current_ns = top->current_ns;
		if (top->has_ns) gf_xml_pop_namespaces(top->node);
		gf_free(top);
		gf_list_rem_last(parser->node_stack);

		if (parser->load->flags & GF_SM_LOAD_FOR_PLAYBACK) {
			switch (node->sgprivate->tag) {
			case TAG_SVG_animateMotion:
				/*
				try to init animateMotion once all children have been parsed
				to make sure we get the mpath child if any, however, we are still not sure
				if the target is known. We can force initialisation
				because mpath children (if any have been parsed)
				*/
			{
				u32 i, count;
				SVG_DeferredAnimation *anim = NULL;
				count = gf_list_count(parser->deferred_animations);
				for (i = 0; i < count; i++) {
					anim = gf_list_get(parser->deferred_animations, i);
					if (anim->animation_elt == node) break;
					else anim = NULL;
				}
				if (anim) {
					if (svg_parse_animation(parser, gf_node_get_graph((GF_Node *)node), anim, NULL, 1)) {
						svg_delete_deferred_anim(anim, parser->deferred_animations);
					}
				}
			}
			break;
			case TAG_SVG_script:
			case TAG_SVG_handler:
				/*init script once text script is loaded*/
				gf_node_init((GF_Node *)node);
				break;
			}
			/*if we have associated event listeners, trigger the onLoad, only in playback mode */
			if (node->sgprivate->interact && node->sgprivate->interact->dom_evt) {
				GF_DOM_Event evt;
				memset(&evt, 0, sizeof(GF_DOM_Event));
				evt.type = GF_EVENT_LOAD;
				gf_dom_event_fire((GF_Node*)node, &evt);
			}

		}
	}
#ifdef SKIP_UNKNOWN_NODES
	else {
		if (top->unknown_depth) {
			top->unknown_depth--;
			if (!top->unknown_depth) {
				/*this is not 100% correct, we should track which unsupported node changed the namespace*/
				parser->current_ns = top->current_ns;
				if (parser->command_depth) parser->command_depth --;
			}
		} else if (parser->command_depth) {
			parser->command_depth--;
		} else {
			svg_report(parser, GF_BAD_PARAM, "SVG depth mismatch");
		}
	}
#endif

#endif //SKIP_ALL
}

static void svg_text_content(void *sax_cbck, const char *text_content, Bool is_cdata)
{
#ifndef SKIP_ALL
	GF_SVG_Parser *parser = (GF_SVG_Parser *)sax_cbck;
	SVG_NodeStack *top = (SVG_NodeStack *)gf_list_last(parser->node_stack);
	SVG_Element *elt = (top ? top->node : NULL);
	GF_DOMText *text;
	Bool skip_text;
	u32 tag;

	if (top && top->unknown_depth && !parser->command_depth) return;
	if (!elt && !parser->command) return;

	tag = (elt ? gf_node_get_tag((GF_Node *)elt) : 0);

	/*if top is a conditional, create a new text node*/
	if (!elt || tag == TAG_LSR_conditional) {
		GF_CommandField *field;
		if (!parser->command) return;

		field = (GF_CommandField *)gf_list_get(parser->command->command_fields, 0);
		if (parser->command->tag == GF_SG_LSR_NEW_SCENE || parser->command->tag == GF_SG_LSR_ADD) return;

		if (!field || field->field_ptr) return;

		if (field->new_node) {
			svg_report(parser, GF_OK, "Warning: LASeR cannot replace children with a mix of text nodes and elements - ignoring text\n");
			return;
		}

		/*create a text node but don't register it with any node*/
		text = gf_dom_new_text_node(parser->load->scene_graph);
		gf_node_register((GF_Node *)text, NULL);
		text->textContent = gf_strdup(text_content);

		if (field->new_node) {
			field->field_ptr = &field->node_list;
			gf_node_list_add_child(& field->node_list, field->new_node);
			field->new_node = NULL;
			gf_node_list_add_child( & field->node_list, (GF_Node*) text);
		} else if (field->node_list) {
			gf_node_list_add_child(& field->node_list, (GF_Node*) text);
		} else {
			field->new_node = (GF_Node*)text;
			field->field_ptr = &field->new_node;
		}
		return;
	}
	skip_text = GF_TRUE;
	switch (tag) {
	case TAG_DOMFullNode:
	case TAG_SVG_a:
	case TAG_SVG_title:
	case TAG_SVG_desc:
	case TAG_SVG_metadata:
	case TAG_SVG_text:
	case TAG_SVG_tspan:
	case TAG_SVG_textArea:
		skip_text = GF_FALSE;
		break;
	/*for script and handlers only add text if not empty*/
	case TAG_SVG_handler:
	case TAG_SVG_script:
	{
		u32 i, len = (u32) strlen(text_content);
		for (i=0; i<len; i++) {
			if (!strchr(" \n\r\t", text_content[i])) {
				skip_text = GF_FALSE;
				break;
			}
		}
	}
	break;
	}

	if (!skip_text) {
		text = gf_dom_add_text_node((GF_Node *)elt, gf_strdup(text_content));
		text->type = is_cdata ? GF_DOM_TEXT_CDATA : GF_DOM_TEXT_REGULAR;
		gf_node_changed((GF_Node *)text, NULL);
	}
#endif
}

static GF_SVG_Parser *svg_new_parser(GF_SceneLoader *load)
{
	GF_SVG_Parser *parser;
	switch (load->type) {
	case GF_SM_LOAD_XSR:
		if (!load->ctx) return NULL;
		break;
	case GF_SM_LOAD_SVG:
	case GF_SM_LOAD_DIMS:
		break;
	default:
		return NULL;
	}

	GF_SAFEALLOC(parser, GF_SVG_Parser);
	if (!parser) {
		return NULL;
	}
	parser->node_stack = gf_list_new();
	parser->deferred_hrefs = gf_list_new();
	parser->deferred_animations = gf_list_new();
	parser->deferred_listeners = gf_list_new();
	parser->peeked_nodes = gf_list_new();

	parser->sax_parser = gf_xml_sax_new(svg_node_start, svg_node_end, svg_text_content, parser);
	parser->load = load;
	load->loader_priv = parser;
	if (load->ctx) load->ctx->is_pixel_metrics = GF_TRUE;

	/*to cope with old files not signaling XMLNS, add the SVG NS by default*/
	gf_sg_add_namespace(parser->load->scene_graph, "http://www.w3.org/2000/svg", NULL);
	parser->current_ns = GF_XMLNS_SVG;

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		svg_report(parser, GF_OK, NULL);
	}
#endif

	return parser;
}

static void svg_flush_animations(GF_SVG_Parser *parser)
{
	if (!parser) return;
	while (gf_list_count(parser->deferred_animations)) {
		SVG_DeferredAnimation *anim = (SVG_DeferredAnimation *)gf_list_get(parser->deferred_animations, 0);
		svg_parse_animation(parser, parser->load->scene_graph, anim, NULL, 2);
		svg_delete_deferred_anim(anim, parser->deferred_animations);
	}
}

static void gf_sm_svg_flush_state(GF_SVG_Parser *parser)
{
	while (gf_list_count(parser->node_stack)) {
		SVG_NodeStack *st = (SVG_NodeStack *)gf_list_last(parser->node_stack);
		gf_list_rem_last(parser->node_stack);
		gf_free(st);
	}
	/*FIXME - if there still are som deferred listeners, we should pass them to the scene graph
	and wait for the parent to be defined*/
	while (gf_list_count(parser->deferred_listeners)) {
		GF_Node *l = (GF_Node *)gf_list_last(parser->deferred_listeners);
		gf_list_rem_last(parser->deferred_listeners);
		/*listeners not resolved are not inserted in the tree - destroy them*/
		gf_node_register(l, NULL);
		gf_node_unregister(l, NULL);
	}
}

static GF_Err gf_sm_load_initialize_svg(GF_SceneLoader *load, const char *str_data, Bool is_fragment)
{
	GF_Err e;
	GF_SVG_Parser *parser;

	if (str_data) {
		char BOM[6];
		BOM[0] = str_data[0];
		BOM[1] = str_data[1];
		BOM[2] = str_data[2];
		BOM[3] = str_data[3];
		BOM[4] = BOM[5] = 0;
		parser = svg_new_parser(load);
		if (!parser) return GF_BAD_PARAM;

		if (is_fragment) parser->has_root = 2;
		e = gf_xml_sax_init(parser->sax_parser, (unsigned char*)BOM);
		if (e) {
			svg_report(parser, e, "Error initializing SAX parser: %s", gf_xml_sax_get_error(parser->sax_parser) );
			return e;
		}
		str_data += 4;

	} else if (load->fileName) {
		parser = svg_new_parser(load);
		if (!parser) return GF_BAD_PARAM;
	} else {
		return GF_BAD_PARAM;
	}

	/*chunk parsing*/
	if (load->flags & GF_SM_LOAD_CONTEXT_READY) {
		u32 i;
		GF_StreamContext *sc;
		if (!load->ctx) return GF_BAD_PARAM;

		/*restore context - note that base layer are ALWAYS declared BEFORE enhancement layers with gpac parsers*/
		i=0;
		while ((sc = (GF_StreamContext*)gf_list_enum(load->ctx->streams, &i))) {
			switch (sc->streamType) {
			case GF_STREAM_SCENE:
				if (!parser->laser_es) parser->laser_es = sc;
				break;
			default:
				break;
			}
		}
		/*need at least one scene stream - FIXME - accept SVG as root? */
		if (!parser->laser_es) return GF_BAD_PARAM;
		GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("SVG: MPEG-4 LASeR / DIMS Scene Chunk Parsing"));
	} else {
		GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[Parser] %s Scene Parsing: %s\n", ((load->type==GF_SM_LOAD_SVG) ? "SVG" : ((load->type==GF_SM_LOAD_XSR) ? "LASeR" : "DIMS")), load->fileName));
	}

	if (str_data)
		return gf_xml_sax_parse(parser->sax_parser, str_data);

	return GF_OK;
}


GF_Err load_svg_run(GF_SceneLoader *load)
{
	u32 in_time;
	GF_Err e;
	GF_SVG_Parser *parser = (GF_SVG_Parser *)load->loader_priv;

	if (!parser) {
		e = gf_sm_load_initialize_svg(load, NULL, GF_FALSE);
		if (e) return e;
		parser = (GF_SVG_Parser *)load->loader_priv;
	}

	in_time = gf_sys_clock();
	e = gf_xml_sax_parse_file(parser->sax_parser, (const char *)load->fileName, svg_progress);
	svg_flush_animations(parser);
	gf_sm_svg_flush_state(parser);
	if (parser->last_error<0) e = parser->last_error;

	if (e<0) return svg_report(parser, e, "Unable to parse file %s: %s", load->fileName, gf_xml_sax_get_error(parser->sax_parser) );
	GF_LOG(GF_LOG_INFO, GF_LOG_PARSER, ("[Parser] Scene parsed and Scene Graph built in %d ms\n", gf_sys_clock() - in_time));

	return e;

}

static void load_svg_done(GF_SceneLoader *load)
{
	SVG_SAFExternalStream *st;
	GF_SVG_Parser *parser = (GF_SVG_Parser *)load->loader_priv;
	if (!parser) return;

	gf_sm_svg_flush_state(parser);

	gf_list_del(parser->node_stack);
	gf_list_del(parser->deferred_hrefs);
	gf_list_del(parser->deferred_listeners);
	gf_list_del(parser->peeked_nodes);
	/* reset animations */
	gf_list_del(parser->deferred_animations);
	if (parser->sax_parser) {
		gf_xml_sax_del(parser->sax_parser);
	}
	st = parser->streams;
	while (st) {
		SVG_SAFExternalStream *next = st->next;
		gf_free(st->stream_name);
		gf_free(st);
		st = next;
	}
	gf_free(parser);
	load->loader_priv = NULL;
}

GF_Err load_svg_parse_string(GF_SceneLoader *load, const char *str)
{
	GF_Err e;
	GF_SVG_Parser *parser = (GF_SVG_Parser *)load->loader_priv;

	if (!parser) {
		e = gf_sm_load_initialize_svg(load, str, GF_FALSE);
		parser = (GF_SVG_Parser *)load->loader_priv;
	} else {
		e = gf_xml_sax_parse(parser->sax_parser, str);
	}
	if (e<0) svg_report(parser, e, "Unable to parse chunk: %s", parser ? gf_xml_sax_get_error(parser->sax_parser) : "no parser");
	else e = parser->last_error;


	svg_flush_animations(parser);

	/*if error move to done state and wait for next XML chunk*/
	if (e) load_svg_done(load);

	return e;
}

static GF_Err load_svg_suspend(GF_SceneLoader *load, Bool suspend)
{
	GF_SVG_Parser *parser = (GF_SVG_Parser *)load->loader_priv;
	if (parser) gf_xml_sax_suspend(parser->sax_parser, suspend);
	return GF_OK;
}


GF_Err gf_sm_load_init_svg(GF_SceneLoader *load)
{
	load->process = load_svg_run;
	load->done = load_svg_done;
	load->parse_string = load_svg_parse_string;
	load->suspend = load_svg_suspend;

	return GF_OK;

}

GF_Node *gf_sm_load_svg_from_string(GF_SceneGraph *in_scene, char *node_str)
{
	GF_Err e;
	GF_SVG_Parser *parser;
	GF_Node *node;
	GF_SceneLoader ctx;
	memset(&ctx, 0, sizeof(GF_SceneLoader));
	ctx.scene_graph = in_scene;
	ctx.type = GF_SM_LOAD_SVG;

	e = gf_sm_load_initialize_svg(&ctx, node_str, GF_TRUE);

	parser = (GF_SVG_Parser *)ctx.loader_priv;

	if (e != GF_OK) {
		if (parser) {
			if (parser->fragment_root) gf_node_unregister(parser->fragment_root, NULL);
			parser->fragment_root=NULL;
		}
		load_svg_done(&ctx);
		return NULL;
	}

	node = parser ? parser->fragment_root : NULL;
	/*don't register*/
	if (node) node->sgprivate->num_instances--;
	load_svg_done(&ctx);
	return node;
}

#endif
