function isAlien(a) {
   return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) {
    return isObject(a) && a.constructor == Array;
}

function isBoolean(a) {
    return typeof a == 'boolean';
}

function isEmpty(o) {
    var i, v;
    if (isObject(o)) {
        for (i in o) {
            v = o[i];
            if (isUndefined(v) && isFunction(v)) {
                return false;
            }
        }
    }
    return true;
}

function isFunction(a) {
    return typeof a == 'function';
}

function isNull(a) {
    return typeof a == 'object' && !a;
}

function isNumber(a) {
    return typeof a == 'number' && isFinite(a);
}

function isObject(a) {
    return (a && typeof a == 'object') || isFunction(a);
}

function isString(a) {
    return typeof a == 'string';
}

function isUndefined(a) {
    return typeof a == 'undefined';
}

function Tag(closing, single, name, attributes, tag, text, open, close, length)
{
	this.closing = closing;		// boolean - if true, tag is a closing tag.
	this.single = single;		// boolean - if true, tag has no closing tag.
	this.name = name;		// String - tag identifier including the namespace.
	this.attributes = attributes;	// Object - attributes object, null if no attributes.
	this.tag = tag;			// String - the open tag text
	this.text = text;		// String - text between opening closing tags, or null if single tag
	this.open = open;		// numeric - the position in the source text where the tag starts
	this.close = close;		// numeric - the position in the source text where the tag ends
	this.length = length;		// numeric - the length of the tag
}

function Attribute(name, value)
{
	this.name = name;	// String - attribute identifier
	this.value = value;	// String - attribute value
}

function parseTag(tagstr)
{
	var newtag=null;
	var name = '';
	var attribute = '';
	var single = false;
	var closing = false;
	var namemode = true;
	var arr= new Array();
	var count=0;
	
	for(var i=0; i<tagstr.length; i++)
	{
		var curr = tagstr.charAt(i);

		if(curr == "<" || curr == ">") continue;

		if(curr == " ")
		{
			if(namemode)
				namemode = false;
			else
			{
				attribute = attribute.replace(/"/g, ""); 			// clear the quotes
				var pos = attribute.indexOf("=");				// find the equals sign

				if(pos > -1)
				{
					var attrname = attribute.substring(0,pos);		// Extract the name.
					var value = attribute.substring(pos+1);			// Extract the value.
					arr[count] = new Attribute(attrname, unescape(value));					
					count++;
				}
				attribute="";							//clear the string for the next attribute
			}
		}
		else if(curr == "/" && name == "")
			closing = true;
		else if(curr == "/" && name != "")
		{
			single = true;
			namemode = false;
		}
		else if(namemode)
			name += curr;
		else
			attribute += curr;
	}

	if(name != "")
	{
		// single, name, attributes, text, open, close, taglen
		newtag = new Tag(closing, single, name, arr, tagstr, '', 0, 0, tagstr.length);
	}

	return newtag;
}

function tagParser(source, debugflag)
{
	var open = 0;
	var close = 0;
	var str = source;
	var count = source.length;
	var loop = 0;
	var currpos = 0;
	var tags = new Array();
	var validate = false;  // to be implemented, validate while parsing file

//document.write("source length = " + source.length + "<br>\n");
	while(open > -1)
	{
		//Find Next Tag
		open = str.search("<");
  		close = str.search(">");
  		var currtag = str.substr(open, close-open+1);
  		
		if(debugflag)
			alert('open = '+open+'\nclose = '+close+'\ncurrtag = ' + currtag);

		if(open < 0) break;

		var ATag = parseTag(currtag);

		if(ATag.closing)
		{
			var i;

			if(debugflag)
				alert("close name = " + ATag.name);

			for(i=loop-1;i>-1;i--)
			{
				if(tags[i].name == ATag.name)
				{
					if(debugflag) 
						alert('close = '+(close+1)+'\ncurrpos = '+currpos+'\nclose pos = '+(currpos + close+1));

					tags[i].close = currpos + close+1;

					if(debugflag) 
						alert('text open pos = '+(tags[i].open + tags[i].length));
					if(debugflag) 
						alert('text close pos = '+(currpos + open));

					tags[i].text = source.substring(tags[i].open + tags[i].length, currpos + open);
					break;
				}
			}

			if(i == -1)
			{	
				break;
			}
		}
		else
		{
			if(debugflag)
				alert("open name = " + ATag.name);

			if(debugflag)
				alert('currpos = '+currpos);
			ATag.open = currpos + open;
			if(ATag.single)
				ATag.close = currpos + close+1;

			tags[loop] = ATag;
			loop++;
		}

  		str = str.substring(close+1, count+1);

		if(str)
		{
			if(debugflag)
				alert(str);
			currpos = source.indexOf(str, currpos);
			
		}
	}

	/*for(var i=0; i<loop; i++)
	{
		//alert(tags[i].name + "\nsingle = " + tags[i].single + "\n");
		//var text = source.substring(tags[i].open, tags[i].close);
		if(tags[i].single)
			alert(tags[i].name + "\n" + tags[i].tag);
		else
			alert(tags[i].name + "\n" + tags[i].text);
	}*/

	return tags;
}

function getTextBetweenTags(source, tagName)
{
	var openTagRE = new RegExp("<"+tagName+">", "gi");
	var closeTagRE = new RegExp("</"+tagName+">", "gi");
	var openpos = source.search(openTagRE);
	var closepos = source.search(closeTagRE);

	if (openpos == -1 || closepos == -1 || closepos <= openpos)
		return false;
		
	openpos += tagName.length+2;
	
	return source.substring(openpos, closepos);
}

function isParent(child, parent)
{
	if(child.open > parent.open && child.close < parent.close)
		return true;
}

function isChild(parent, child)
{
	return !isParent(child, parent);
}

function findTagsByName(tagarr, name)
{
	var arr=null;

	if(tagarr)
	{
		var j=0;
		arr = new Array();
		for(var i=0; i<tagarr.length; i++)
		{
			if(tagarr[i].name == name)
			{
				arr[j] = tagarr[i];
				j++;
			}
		}
	}

	return arr;
}

function findTagByName(tagarr, name, index)
{
	var arr=null;
	var start=0;

	if(isNumber(index))
		start=parseInt(index);

	if(tagarr)
	{
		if(tagarr.length > start)
		{
			var j=0;
			arr = new Array();
		
			for(var i=start; i<tagarr.length; i++)
			{
				if(tagarr[i].name == name)
				{
					arr = tagarr[i];
					break;
				}
			}
		}
	}

	return arr;
}

function findTagById(tagarr, ID)
{
	var retval=null;

	if(tagarr)
	{
		for(var i=0; i<tagarr.length; i++)
		{
			//alert("name = " + tagarr[i].name);
			if(tagarr[i].attributes)
			{
				if(tagarr[i].attributes.length > 0)
				{
					for(var j=0; j<tagarr[i].attributes.length; j++)
					{
						var name = tagarr[i].attributes[j].name;
						var value = tagarr[i].attributes[j].value;
						name = name.toLowerCase();
						//alert("name = " + name + "\nvalue = " + value);
						if(name == 'id' && value == ID)
						{
							retval = tagarr[i];
							break;
						}
					}
				}
			}
			if(retval != null) break;
		}
	}

	return retval;
}

function findTagByNameId(tagarr, name, ID)
{
	var retval=null;

	if(tagarr)
	{
		for(var i=0; i<tagarr.length; i++)
		{
			//alert("name = " + tagarr[i].name);
			if(tagarr[i].name == name && tagarr[i].attributes)
			{
				//alert("attr len = " + tagarr[i].attributes.length);
				if(tagarr[i].attributes.length > 0)
				{
					for(var j=0; j<tagarr[i].attributes.length; j++)
					{
						var name = tagarr[i].attributes[j].name;
						var value = tagarr[i].attributes[j].value;
						name = name.toLowerCase();
						//alert("name = " + name + "\nvalue = " + value);
						if(name == 'id' && value == ID)
						{
							retval = tagarr[i];
							break;
						}
					}
				}
			}
			if(retval != null) break;
		}
	}

	return retval;
}
