﻿/*
Microformats Test Runner 0.1
Author: Glenn Jones
    
The MIT License
	
Copyright (c) 2008 Glenn Jones
	
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
	
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
	
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
	
*/


function UtilitiesBase() {
    var me;
    if (this.constructor == UtilitiesBase) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }

    me.listeners = new Array();
    //    me.ufxtractUrl = 'http://localhost/BacknetworkLab/ufXtract/';
    //    me.proxyUrl = 'http://localhost:49648/uftest/proxies/proxy.aspx';

    me.ufxtractUrl = 'http://lab.backnetwork.com/ufXtract/';
    me.proxyUrl = 'http://www.ufxtract.com/proxies/proxy.aspx';

}

UtilitiesBase.prototype.addScript = function (url) {
    var script = document.createElement("script");
    script.setAttribute("type", "text/javascript");

    if (url.indexOf('?') > -1)
        url += '&';
    else
        url += '?';

    url += 'rand=' + Math.random();
    script.setAttribute("src", url);
    document.getElementsByTagName('head')[0].appendChild(script);
    return script;
}

UtilitiesBase.prototype.insertAfter = function (parent, node, referenceNode) {
    parent.insertBefore(node, referenceNode.nextSibling);
}

UtilitiesBase.prototype.getElementsByClassName = function (className) {
    var children = document.getElementsByTagName("*") || document.all;
    var elements = new Array;
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        var classNames = child.className.split(" ");
        for (var j = 0; j < classNames.length; j++) {
            if (classNames[j] == className) {
                elements.push(child);
                break;
            }
        }
    }
    return elements;
}


UtilitiesBase.prototype.addEvent = function (elm, evType, fn, useCapture) {
    // Updated version which captures passed events 
    if (elm.AddEventListener) {
        elm.AddEventListener(evType, fn, useCapture);
        return true;
    } else if (elm.attachEvent) {
        var r = elm.attachEvent('on' + evType, fn);
        this.listeners[this.listeners.length] = [elm, evType, fn];
        return r;
    } else {
        var xEventFn = elm['on' + evType];
        if (typeof elm['on' + evType] != 'function') {
            elm['on' + evType] = fn;
        } else {
            elm['on' + evType] = function (e) { xEventFn(e); fn(e); };
        }
    }
}

UtilitiesBase.prototype.unload = function () {
    // page unload event which removes circular references
    // that may cause memory leaks in IE 5/6
    //	if( window.attachEvent ){
    //	    for (var i = 0; i < this.listeners.length; i++) {
    //		    this.listeners[i][0].detachEvent( 'on' + this.listeners[i][1], this.listeners[i][2] );
    //	    }
    //	}
}

UtilitiesBase.prototype.stopPropagation = function (e) {
    // Use to close down unwanted event propagation/bubbling
    if (window.event) {
        window.event.cancelBubble = true;
    } else if (typeof e != 'undefined') {
        e.stopPropagation();
    }
}


UtilitiesBase.prototype.findEventSource = function (e) {
    if (typeof e == 'undefined')
        var e = window.event;

    var source;
    if (typeof e.target != 'undefined') {
        source = e.target;
    } else if (typeof e.srcElement != 'undefined') {
        source = e.srcElement;
    } else {
        return true;
    }

    if (source.nodeType == 3)
        source = source.parentNode;

    return source;
}











function TestRunner(objectRef) {
    var me;
    if (this.constructor == TestRunner) {
        me = this;
    } else {
        me = arguments[arguments.length - 1];
    }

    UtilitiesBase(me);
    me.objectRef = objectRef;

    me.domLoaded = false;
    me.fixtureJSONLoaded = false;
    me.interfaceBuilt = false;

    me.parsers = new Array();
    me.pages = new Array();

    // Load testfixture while DOM builds
    // This is just for a single page at the moment
    me.pages[0] = new Page(me.objectRef, 0);
    me.pages[0].getFixture(me.objectRef + ".pages[0].loadFixture");

    me.load = function () {
        me.domLoaded = true;
    }


    // Add parser interface objects 
    me.addParserInterface = function (parserInterface) {
        parserInterface.parent = me;
        parserInterface.parentObjectName = me.objectRef;
        parserInterface.parserNumber = me.parsers.length;
        me.parsers[me.parsers.length] = parserInterface;
    }


    // Fixture - start
    // ------------------------------------------------------------------------------

    // Checks key events to open test options form
    me.altKeyCheck = function (e) {
        var keyID = (window.event) ? event.keyCode : e.keyCode;
        var altKey = (window.event) ? event.altKey : e.altKey;
        var ctrlKey = (window.event) ? event.ctrlKey : e.ctrlKey;
        var shiftKey = (window.event) ? event.shiftKey : e.shiftKey;

        // keyID == 0 for Mac Firefox
        if (((keyID == 88) || (keyID == 900)) && (altKey == true)) // Alt X
        {
            me.displayTestOptionForm();
        } else if (ctrlKey && shiftKey && (keyID == 88) || (keyID == 900)) { // Shift Ctrl X
            me.displayTestOptionForm();
        }
    }

    // Displays test options form
    me.displayTestOptionForm = function () {
        if (document.getElementById('testOptionForm')) {
            var testOptionForm = document.getElementById('testOptionForm');
            testOptionForm.style.display = 'block';
        }
        else {
            if (me.domLoaded && me.fixtureJSONLoaded) {
                me.buildTestOptionForm();
                me.buildResultsPanel();
                me.buildJSONPanel();
                // Recall function
                me.displayTestOptionForm();
            }
        }
    }

    // Hide test options form
    me.hideTestOptionForm = function () {
        if (document.getElementById('testOptionForm')) {
            var testOptionForm = document.getElementById('testOptionForm');
            testOptionForm.style.display = 'none';
        }
    }

    // Loads JavaScript modules for parsers
    me.loadJSFile = function () {
        for (var i = 0; i < me.parsers.length; i++) {
            if (me.parsers[i].supportsFormat(me.pages[0].testFixture.format)) {
                for (var j = 0; j < me.parsers[i].externalJavaScript.length; j++) {
                    if (me.parsers[i].externalJavaScript[j][0].toLowerCase() == me.pages[0].testFixture.format.toLowerCase())
                        this.addScript(me.parsers[i].externalJavaScript[j][1]);
                }
            }
        }
    }


    // Builds test options form using DOM inserts
    me.buildTestOptionForm = function () {



        // Creates and appends Test Runner form
        var div = document.createElement('div');
        div.setAttribute('id', 'testOptionForm');
        div.style.display = 'none';

        var img = document.createElement('img');
        img.setAttribute('alt', 'Microformats test runner');
        img.setAttribute('title', 'Microformats test runner');
        img.setAttribute('id', 'testOptionFormLogo');
        img.setAttribute('src', 'http://ufxtract.com/testrunner/images/testrunner.gif');

        var p1 = document.createElement('p');
        p1.appendChild(document.createTextNode('This tool run’s the microformats parser test described in this page and return’s a pass or fail for each test. You can choose which parser’s to test and view the JSON output of each.'));

        var p2 = document.createElement('p');
        p2.appendChild(document.createTextNode('The available parser’s for ' + me.pages[0].testFixture.format));

        var imgClose = document.createElement('img');
        imgClose.setAttribute('alt', 'Close');
        imgClose.setAttribute('title', 'Close');
        imgClose.setAttribute('id', 'testOptionFormCloseButton');
        imgClose.setAttribute('src', 'http://ufxtract.com/testrunner/images/close.gif');
        me.addEvent(imgClose, 'click', me.hideTestOptionForm, false);

        var a = document.createElement('a');
        a.setAttribute('id', 'testOptionFormSiteLink');
        a.setAttribute('href', 'http://ufxtract.com/testrunner/');
        a.appendChild(document.createTextNode('http://ufxtract.com/testrunner/'));

        div.appendChild(img);
        div.appendChild(p1);
        div.appendChild(p2);


        var ul = document.createElement('ul');
        ul.setAttribute('id', 'testOptionFormParserList');
        div.appendChild(ul);

        for (var i = 0; i < me.parsers.length; i++) {
            if (me.parsers[i].supportsFormat(me.pages[0].testFixture.format))
                me.addParserChoice(me.parsers[i], i, ul);
        }

        var submitinput = document.createElement('input');
        submitinput.setAttribute('id', 'testOptionFormSubmit');
        submitinput.setAttribute('name', 'testOptionFormSubmit');
        submitinput.setAttribute('type', 'button');
        submitinput.setAttribute('value', 'Run');
        me.addEvent(submitinput, 'click', me.run, false);

        div.appendChild(submitinput);
        div.appendChild(imgClose);
        div.appendChild(a);
        document.body.appendChild(div);

        Drag.init(div, div);
        div.style.top = 200 + 'px';
        div.style.left = 200 + 'px';

    }

    // Builds the parser choice checkboxs in a list
    me.addParserChoice = function (parserInterface, number, parentNode) {
        var checkbox = document.createElement('input');
        checkbox.setAttribute('id', "parser" + number);
        checkbox.setAttribute('type', 'checkbox');
        checkbox.setAttribute('checked', 'checked');
        var span = document.createElement('span');
        span.appendChild(document.createTextNode(parserInterface.name));
        var li = document.createElement('li');
        li.appendChild(checkbox);
        li.appendChild(span);
        parentNode.appendChild(li);
    }


    // Displays results panel
    me.displayResultsPanel = function () {
        me.hideTestOptionForm();
        if (document.getElementById('resultsPanel')) {
            var resultsPanel = document.getElementById('resultsPanel');
            resultsPanel.style.display = 'block';
        }
    }

    // Hide results panel
    me.hideResultsPanel = function () {
        if (document.getElementById('resultsPanel')) {
            var resultsPanel = document.getElementById('resultsPanel');
            resultsPanel.style.display = 'none';
        }
    }

    // Builds results panel form using DOM inserts
    me.buildResultsPanel = function () {

        // Creates and appends Test Runner form
        var div = document.createElement('div');
        div.setAttribute('id', 'resultsPanel');
        div.style.display = 'none';

        var img = document.createElement('img');
        img.setAttribute('alt', 'Microformats test runner');
        img.setAttribute('title', 'Microformats test runner');
        img.setAttribute('id', 'resultsPanelLogo');
        img.setAttribute('src', 'http://ufxtract.com/testrunner/images/testrunner.gif');

        var imgClose = document.createElement('img');
        imgClose.setAttribute('alt', 'Close');
        imgClose.setAttribute('title', 'Close');
        imgClose.setAttribute('id', 'resultsPanelCloseButton');
        imgClose.setAttribute('src', 'http://ufxtract.com/testrunner/images/close.gif');
        me.addEvent(imgClose, 'click', me.hideResultsPanel, false);
        div.appendChild(img);

        var p1 = document.createElement('p');
        var strong = document.createElement('strong');
        strong.appendChild(document.createTextNode(me.pages[0].testFixture.summary));
        p1.appendChild(strong);
        p1.appendChild(document.createElement('br'));
        p1.appendChild(document.createTextNode(me.pages[0].testFixture.description));
        div.appendChild(p1);

        var a = document.createElement('a');
        a.setAttribute('id', 'resultsPanelSiteLink');
        a.setAttribute('href', 'http://ufxtract.com/testrunner/');
        a.appendChild(document.createTextNode('http://ufxtract.com/testrunner/'));

        var divScroll = document.createElement('div');
        divScroll.setAttribute('id', 'resultsPanelScrollTable');
        div.appendChild(divScroll);

        var table = document.createElement('table');
        table.setAttribute('id', 'resultsPanelResultsTable');
        table.setAttribute('cellpadding', '0');
        table.setAttribute('cellspacing', '0');
        var thead = document.createElement('thead');
        var tbody = document.createElement('tbody');
        divScroll.appendChild(table);
        table.appendChild(thead);
        table.appendChild(tbody);

        var tr = document.createElement('tr');
        me.addHeaderCell(tr, ' ', '', '');
        me.addHeaderCell(tr, 'Test description', '', '');
        // Add parser cells    
        for (var i = 0; i < me.parsers.length; i++) {
            me.addHeaderCell(tr, me.parsers[i].name, 'parserName', '');
        }
        thead.appendChild(tr);


        for (var i = 0; i < me.pages[0].testFixture.assert.length; i++) {
            me.addAssertRow(tbody, me.pages[0].testFixture.assert[i], i)
        }

        me.addEvent(divScroll, 'mousedown', me.stopPropagation, false);

        var submitJSON = document.createElement('input');
        submitJSON.setAttribute('id', 'resultsPanelOpenJSON');
        submitJSON.setAttribute('name', 'resultsPanelOpenJSON');
        submitJSON.setAttribute('type', 'button');
        submitJSON.setAttribute('value', 'JSON/HTML');
        me.addEvent(submitJSON, 'click', me.displayJSONPanel, false);

        //		var submitHTML = document.createElement('input');
        //		submitHTML.setAttribute( 'id', 'resultsPanelOpenHTML' );
        //		submitHTML.setAttribute( 'name', 'resultsPanelOpenHTML' );
        //		submitHTML.setAttribute( 'type', 'button' );
        //		submitHTML.setAttribute( 'value', 'HTML' );
        //		me.addEvent( submitHTML, 'click', me.displayJSONPanel, false );

        div.appendChild(submitJSON);
        //div.appendChild(submitHTML);
        div.appendChild(imgClose);
        div.appendChild(a);
        document.body.appendChild(div);

        Drag.init(div, div);
        div.style.top = 200 + 'px';
        div.style.left = 200 + 'px';

    }


    me.addAssertRow = function (tbody, assert, number) {
        var tr = document.createElement('tr');
        me.addCell(tr, number + 1, 'assertNumber', '');
        me.addCell(tr, assert.comment, 'assertComment', '');

        // Add parser cells    
        for (var i = 0; i < me.parsers.length; i++) {
            me.addCell(tr, '-', 'parserTestResults', '');
        }

        tbody.appendChild(tr);
    }

    me.addCell = function (row, text, className, id) {
        var td = document.createElement('td');
        td.appendChild(document.createTextNode(text));
        if (className != '')
            td.className = className;
        if (id != '')
            td.setAttribute('id', id);
        row.appendChild(td);
    }

    me.addHeaderCell = function (row, text, className, id) {
        var th = document.createElement('th');
        th.appendChild(document.createTextNode(text));
        if (className != '')
            th.className = className;
        if (id != '')
            th.setAttribute('id', id);
        row.appendChild(th);
    }

    me.addTestResultImage = function (element, testResult) {
        var img = document.createElement('img');
        img.setAttribute('alt', testResult);
        img.setAttribute('title', testResult);
        img.setAttribute('src', 'http://ufxtract.com/testrunner/images/' + testResult + '.gif');
        element.innerHTML = '';
        element.appendChild(img);
    }


    // ----------------------------------------------------------------------


    me.run = function () {
        for (var i = 0; i < me.parsers.length; i++) {
            if (document.getElementById("parser" + i)) {
                var checkbox = document.getElementById("parser" + i);
                if (checkbox.checked) {
                    me.parsers[i].getJSON(me.pages[0].testFixture, 0);
                    me.parsers[i].selected = true;
                }
            }
        }
    }


    // Collects incoming results and displays them
    me.collectResults = function (results, parserNumber) {

        me.displayResultsPanel();
        var table = document.getElementById('resultsPanelResultsTable');
        var tbody = table.getElementsByTagName("tbody")[0];

        for (var i = 0; i < results.length; i++) {
            var cell = tbody.rows[i].cells[2 + parserNumber];
            me.addTestResultImage(cell, results[i].testStatus);

        }
    }



    me.parserUfJSONLoaded = function (parserNumber) {
        var parser = me.parsers[parserNumber];

    }



    // Displays results panel
    me.displayJSONPanel = function () {
        me.hideTestOptionForm();
        if (document.getElementById('JSONPanel')) {
            var resultsPanel = document.getElementById('JSONPanel');
            resultsPanel.style.display = 'block';
            me.sitchJSONCodeDisplay();
        }
    }

    // Hide results panel
    me.hideJSONPanel = function () {
        if (document.getElementById('JSONPanel')) {
            var resultsPanel = document.getElementById('JSONPanel');
            resultsPanel.style.display = 'none';
        }
    }



    // Builds results panel form using DOM inserts
    me.buildJSONPanel = function () {

        // Creates and appends Test Runner form
        var div = document.createElement('div');
        div.setAttribute('id', 'JSONPanel');
        div.style.display = 'none';

        var img = document.createElement('img');
        img.setAttribute('alt', 'Microformats test runner');
        img.setAttribute('title', 'Microformats test runner');
        img.setAttribute('id', 'JSONPanelLogo');
        img.setAttribute('src', 'http://ufxtract.com/testrunner/images/testrunner.gif');

        var imgClose = document.createElement('img');
        imgClose.setAttribute('alt', 'Close');
        imgClose.setAttribute('title', 'Close');
        imgClose.setAttribute('id', 'JSONPanelCloseButton');
        imgClose.setAttribute('src', 'http://ufxtract.com/testrunner/images/close.gif');
        me.addEvent(imgClose, 'click', me.hideJSONPanel, false);
        div.appendChild(img);

        var p1 = document.createElement('p');
        p1.appendChild(document.createTextNode('Select a parser: '));
        var select = document.createElement('select');
        select.setAttribute('id', 'JSONPanelSelect');

        for (var i = 0; i < me.parsers.length; i++) {
            select.options[select.options.length] = new Option(me.parsers[i].name, i, false, false);
        }
        select.options[select.options.length] = new Option("HTML", "html", false, false);

        me.addEvent(select, 'change', me.sitchJSONCodeDisplay, false);
        me.addEvent(select, 'mousedown', me.stopPropagation, false);

        p1.appendChild(select);
        div.appendChild(p1);

        var a = document.createElement('a');
        a.setAttribute('id', 'JSONPanelSiteLink');
        a.setAttribute('href', 'http://ufxtract.com/testrunner/');
        a.appendChild(document.createTextNode('http://ufxtract.com/testrunner/'));

        var divCodeBox = document.createElement('textarea');
        divCodeBox.setAttribute('id', 'JSONPanelCodeBox');
        div.appendChild(divCodeBox);
        me.addEvent(divCodeBox, 'mousedown', me.stopPropagation, false);

        div.appendChild(imgClose);
        div.appendChild(a);
        document.body.appendChild(div);

        Drag.init(div, div);
        div.style.top = 200 + 'px';
        div.style.left = 200 + 'px';

    }

    // Converts JSON into a string and then formats for viewing
    me.loadPrittyPrinter = function (parserNumber) {
        if (document.getElementById('JSONPanelCodeBox')) {
            var codeBox = document.getElementById('JSONPanelCodeBox');
            var uf = Object.toJSON(me.parsers[parserNumber].uf);
            codeBox.value = js_beautify(uf, 4, ' ');
        }
    }

    me.sitchJSONCodeDisplay = function () {
        if (document.getElementById('JSONPanelSelect')) {
            var select = document.getElementById('JSONPanelSelect');
            if (select.options[select.options.selectedIndex].value == "html") {
                var codeBox = document.getElementById('JSONPanelCodeBox');
                var ufhtml = document.getElementById('uf');
                codeBox.value = ufhtml.innerHTML;
            } else {
                me.loadPrittyPrinter(select.options[select.options.selectedIndex].value);
            }
        }
    }


    //------------------------------------------


    // dom-drag.js
    // 09.25.2001
    // www.youngpup.net
    // 10.28.2001 - fixed minor bug where events
    // sometimes fired off the handle, not the root.
    var Drag = {

        obj: null,

        init: function (o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper) {
            o.onmousedown = Drag.start;

            o.hmode = bSwapHorzRef ? false : true;
            o.vmode = bSwapVertRef ? false : true;

            o.root = oRoot && oRoot != null ? oRoot : o;

            if (o.hmode && isNaN(parseInt(o.root.style.left))) o.root.style.left = "0px";
            if (o.vmode && isNaN(parseInt(o.root.style.top))) o.root.style.top = "0px";
            if (!o.hmode && isNaN(parseInt(o.root.style.right))) o.root.style.right = "0px";
            if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px";

            o.minX = typeof minX != 'undefined' ? minX : null;
            o.minY = typeof minY != 'undefined' ? minY : null;
            o.maxX = typeof maxX != 'undefined' ? maxX : null;
            o.maxY = typeof maxY != 'undefined' ? maxY : null;

            o.xMapper = fXMapper ? fXMapper : null;
            o.yMapper = fYMapper ? fYMapper : null;

            o.root.onDragStart = new Function();
            o.root.onDragEnd = new Function();
            o.root.onDrag = new Function();
        },

        start: function (e) {
            var o = Drag.obj = this;
            e = Drag.fixE(e);
            var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom);
            var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right);
            o.root.onDragStart(x, y);

            o.lastMouseX = e.clientX;
            o.lastMouseY = e.clientY;

            if (o.hmode) {
                if (o.minX != null) o.minMouseX = e.clientX - x + o.minX;
                if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;
            } else {
                if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x;
                if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;
            }

            if (o.vmode) {
                if (o.minY != null) o.minMouseY = e.clientY - y + o.minY;
                if (o.maxY != null) o.maxMouseY = o.minMouseY + o.maxY - o.minY;
            } else {
                if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y;
                if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;
            }

            document.onmousemove = Drag.drag;
            document.onmouseup = Drag.end;

            return false;
        },

        drag: function (e) {
            e = Drag.fixE(e);
            var o = Drag.obj;

            var ey = e.clientY;
            var ex = e.clientX;
            var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom);
            var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right);
            var nx, ny;

            if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
            if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
            if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
            if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);

            nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
            ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));

            if (o.xMapper) nx = o.xMapper(y)
            else if (o.yMapper) ny = o.yMapper(x)

            Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
            Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
            Drag.obj.lastMouseX = ex;
            Drag.obj.lastMouseY = ey;

            Drag.obj.root.onDrag(nx, ny);
            return false;
        },

        end: function () {
            document.onmousemove = null;
            document.onmouseup = null;
            Drag.obj.root.onDragEnd(parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]),
										parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"]));
            Drag.obj = null;
        },

        fixE: function (e) {
            if (typeof e == 'undefined') e = window.event;
            if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
            if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
            return e;
        }
    };


    me.addEvent(window, 'load', me.load, false);
    me.addEvent(document, 'keydown', me.altKeyCheck, false);
    me.addEvent(window, 'unload', me.unload, false);

}
Inherit(UtilitiesBase, TestRunner);





function ParserInterface() {
    var me;
    if (this.constructor == ParserInterface) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    UtilitiesBase(me);

    me.parent;
    me.parentObjectName;
    me.parserNumber;

    me.name;
    me.formats = new Array();
    me.externalJavaScript = new Array();
    me.serviceUrl;
    me.supportsPJSON = true;
    me.supportsFragment = true;

    me.ufUrl;
    me.ufUrlNotEscaped;
    me.uf = new Object();
    me.ufRaw = new Object();
    me.results = new Array();

    me.reset = function () {
        me.ufUrl = null;
        me.ufUrlNotEscaped = null;
        me.uf = null;
        me.results = new Array();
        me.testFixture = null;
        me.pageNumber = null;
    }

    // Checks for supports of a given format
    me.supportsFormat = function (format) {
        var found = false;
        for (var i = 0; i < me.formats.length; i++) {
            if (String(me.formats[i]).toLowerCase() == format.toLowerCase())
                found = true;
        }
        return found;
    }


    // Testers
    //--------------------------------------------------------------------------------


    // Runs the test fixture using this parser
    me.run = function () {

        me.results = new Array();

        for (var i = 0; i < me.testFixture.assert.length; i++) {

            var test = me.testFixture.assert[i].test;
            var result = me.testFixture.assert[i].result;
            var comment = me.testFixture.assert[i].comment;
            var testStatus = "none";

            if (result.indexOf("IsEqualTo(") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.isEqualTo(test, result, comment));
            }
            else if (result.indexOf("IsEqualToCaseInsensitive(") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.isEqualToCaseInsensitive(test, result, comment));
            }
            else if (result.indexOf("IsEqualToISODate(") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.isEqualToISODate(test, result, comment));
            }
            else if (result.indexOf("IsEqualToGeo(") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.isEqualToGeo(test, result, comment));
            }
            else if (result.indexOf("IsEqualToPhoneNumber(") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.isEqualToPhoneNumber(test, result, comment));
            }
            else if (result.indexOf("IsTrue()") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.isTrue(test, result, comment));
            }
            else if (result.indexOf("IsFalse()") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.isFalse(test, result, comment));
            }
            else if (result.indexOf("HasProperty(") > -1) {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], me.HasProperty(test, result, comment));
            }
            else {
                me.results[me.results.length] = new Result(me.pageNumber, i, me.testFixture.assert[i], 'unnone');
            }
        }

        // Call parent object function on complete
        if (me.parent.constructor == TestRunner)
            me.parent.collectResults(me.results, me.parserNumber);

    }



    // The "isEqualTo" tester
    me.isEqualTo = function (test, result, comment) {
        resultValue = result.substring(11, result.length - 2)
        testValue = String(me.getNodeVaue(test));
        if (testValue != '' && resultValue == testValue)
            return "passed";
        else
            return "failed";
    }

    // The "isEqualToCaseInsensitive" tester
    me.isEqualToCaseInsensitive = function (test, result, comment) {
        resultValue = result.substring(26, result.length - 2)
        testValue = String(me.getNodeVaue(test));
        if (testValue != '' && resultValue.toLowerCase() == testValue.toLowerCase())
            return "passed";
        else
            return "failed";
    }

    // The "isEqualToISODate" tester
    me.isEqualToISODate = function (test, result, comment) {
        output = "failed"
        resultValue = String(result.substring(18, result.length - 2));
        testValue = String(me.getNodeVaue(test));
        if (testValue != '' && resultValue.toLowerCase() == testValue.toLowerCase()) {
            output = "passed";
        } else {
            if (CompareISODates(testValue, resultValue))
                output = "passed";
        }
        return output;
    }

    // The "isEqualToGeo" tester
    me.isEqualToGeo = function (test, result, comment) {
        output = "failed"
        resultValue = String(result.substring(14, result.length - 2));
        testValue = String(me.getNodeVaue(test));
        if (testValue != '' && resultValue.toLowerCase() == testValue.toLowerCase()) {
            output = "passed";
        } else {
            if (CompareRFC2426Geo(testValue, resultValue))
                output = "passed";
        }
        return output;
    }


    // The "isEqualToPhoneNumber" tester
    me.isEqualToPhoneNumber = function (test, result, comment) {
        output = "failed"
        resultValue = result.substring(22, result.length - 2)
        testValue = String(me.getNodeVaue(test));

        if (testValue != '' && resultValue.toLowerCase() == testValue.toLowerCase()) {
            output = "passed";
        } else {
            if (ComparePhoneNumbers(testValue, resultValue))
                output = "passed";
        }
        return output;
    }


    // The "isTrue" tester
    me.isTrue = function (test, result, comment) {
        testValue = me.getNodeVaue(test);
        if (typeof (testValue) != 'undefined' && String(testValue.toLowerCase()) == 'true')
            return "passed";
        else
            return "failed";
    }

    // The "isFalse" tester
    me.isFalse = function (test, result, comment) {
        testValue = me.getNodeVaue(test);
        if (typeof (testValue) != 'undefined' && String(testValue.toLowerCase()) == 'false')
            return "passed";
        else
            return "failed";
    }

    // The "HasProperty" tester
    me.HasProperty = function (test, result, comment) {

        var testValue = me.getNodeVaue(test);
        var resultValue = result.substring(12, result.length - 1).toLowerCase();
        var output = "failed";


        var nodeHasProperty = true;
        if (testValue == 'undefined' || testValue == null)
            nodeHasProperty = false;

        if (nodeHasProperty == true && resultValue == "true")
            output = "passed";

        if (nodeHasProperty == false && resultValue == 'false')
            output = "passed";

        return output;
    }

    // A helper function to finds a value of a given JSON property
    me.getNodeVaue = function (test) {
        // Gets a value from a JSON object
        // vcard[0].url[0]
        var output = null;
        try {
            var currentOject = me.uf;
            var arrayDots = test.split(".");
            for (var i = 0; i < arrayDots.length; i++) {
                if (arrayDots[i].indexOf('[') > -1) {
                    // Reconstructs and adds access to array of objects
                    var arrayAB = arrayDots[i].split("[");
                    var arrayName = arrayAB[0];
                    var arrayPosition = Number(arrayAB[1].substring(0, arrayAB[1].length - 1));

                    if (currentOject[arrayName] != null || currentOject[arrayName] != 'undefined') {
                        if (currentOject[arrayName][arrayPosition] != null || currentOject[arrayName][arrayPosition] != 'undefined')
                            currentOject = currentOject[arrayName][arrayPosition];

                    }
                    else {
                        currentObject = null;
                    }

                }
                else {
                    // Adds access to a property using property array ["given-name"]
                    if (currentOject[arrayDots[i]] != null || currentOject[arrayDots[i]] != 'undefined')
                        currentOject = currentOject[arrayDots[i]];
                }
            }
            output = currentOject;
        } catch (err) {
            // Add error capture
            output = null;
        }
        return output;
    }

    //--------------------------------------------------------------------------------
}
Inherit(UtilitiesBase, ParserInterface);


// Loads JSON from a parser API service
ParserInterface.prototype.getJSON = function (testFixture, pageNumber) {
    this.testFixture = testFixture;
    this.pageNumber = pageNumber;
    var thisDocument = '';
    if (!this.supportsPJSON || !this.supportsFragment) {

        // Uses the proxy service to create fragment and PJSON support
        thisDocument = escape(document.location.href);
        this.ufUrl = this.proxyUrl + "?serviceurl=[serviceurl]&fragmentid=[fragmentid]&url=[url]&format=[format]&callback=[callback]";
        this.ufUrl = this.ufUrl.replace("[serviceurl]", escape(this.serviceUrl));
        this.ufUrl = this.ufUrl.replace("[fragmentid]", 'uf');
        this.ufUrl = this.ufUrl.replace("[url]", escape(thisDocument));
        this.ufUrl = this.ufUrl.replace("[format]", testFixture.format.toLowerCase());
        this.ufUrl = this.ufUrl.replace("[callback]", this.parentObjectName + '.parsers[' + this.parserNumber + '].loadJSON');
        this.addScript(this.ufUrl);

    } else {

        thisDocument = document.location.href + "#uf";
        this.ufUrl = this.serviceUrl;
        this.ufUrl = this.ufUrl.replace("[url]", escape(thisDocument));
        this.ufUrl = this.ufUrl.replace("[format]", testFixture.format.toLowerCase());
        this.ufUrl = this.ufUrl.replace("[callback]", this.parentObjectName + '.parsers[' + this.parserNumber + '].loadJSON');
        this.addScript(this.ufUrl);

        this.ufUrlNotEscaped = this.serviceUrl.replace("[url]", thisDocument);
        this.ufUrlNotEscaped = this.ufUrlNotEscaped.replace("[format]", testFixture.format.toLowerCase());

    }

    this.selected = false;
}

// Callback function of loading JSON data from a parser API service
ParserInterface.prototype.loadJSON = function (data) {
    this.uf = data;
    this.ufRaw = data;
    this.run();
}




// Simple result storage object used by ParserInterface
function Result(pageNumber, assertNumber, assert, testStatus) {
    var me;
    if (this.constructor == Result) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    me.pageNumber = pageNumber;
    me.assertNumber = assertNumber;
    me.assert = assert; // object with properties
    me.testStatus = testStatus;
}




function Page(parentObjectName, pageNumber) {
    var me;
    if (this.constructor == Page) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    UtilitiesBase(me);

    me.parentObjectName = parentObjectName;
    me.pageNumber = pageNumber;
    me.testFixture;
    me.hasFixture = false;
    me.parsers = new Array();
    me.uf = new Array();
    me.results = new Array()
}
Inherit(UtilitiesBase, Page);


Page.prototype.loadFixture = function (data) {
    if (typeof (data['test-fixture'][0]) == "object") {
        this.testFixture = data['test-fixture'][0];
        this.hasFixture = true;
        microformatsTestRunner.fixtureJSONLoaded = true;
        microformatsTestRunner.loadJSFile();
    }
}

Page.prototype.getFixture = function () {
    url = this.ufxtractUrl;
    url += '?url=' + document.location.href;
    url += '&format=test-fixture';
    url += '&output=json';
    url += '&callback=' + this.parentObjectName + '.pages[' + this.pageNumber + '].loadFixture';
    this.addScript(url);
}


//------------------------------------------------------------------------

function Inherit(base, derived) {
    for (var i in base.prototype)
        derived.prototype[i] = base.prototype[i];
}






// Create object
var microformatsTestRunner = new TestRunner("microformatsTestRunner");


// Inherits from ParserInterface
function ufxtractParserInterface() {
    var me;
    if (this.constructor == ufxtractParserInterface) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    ParserInterface(me);
}
Inherit(ParserInterface, ufxtractParserInterface);

// Override the base class function
// Callback function of loading JSON data from a parser API service
ufxtractParserInterface.prototype.loadJSON = function (data) {
    data = data.microformats;
    this.uf = data;
    this.ufRaw = data;
    this.run();
}

// ufXtract
var ufxtract = new ufxtractParserInterface();
ufxtract.name = "ufXtract";
ufxtract.formats = ["hCalendar", "hCard", "hEntry", "hResume", "hReview", "adr", "geo", "xfn", "votelinks", , "rel-tag", "rel-license"];
ufxtract.serviceUrl = "http://ufxtract.com/api/default.aspx?url=[url]&format=[format]&output=json&callback=[callback]";
microformatsTestRunner.addParserInterface(ufxtract);


//------------------------------------------------------------------------

// Inherits from ParserInterface
function OptimusParserInterface() {
    var me;
    if (this.constructor == OptimusParserInterface) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    ParserInterface(me);
}
Inherit(ParserInterface, OptimusParserInterface);

// Override the base class function
OptimusParserInterface.prototype.loadJSON = function (data) {

    this.ufRaw = data;

    var ufName = '';
    var optimusName = '';
    if (this.testFixture.format.toLowerCase() == "hcard") {
        ufName = "vcard";
        optimusName = "hcard";
    }
    else if (this.testFixture.format.toLowerCase() == "hcalendar") {
        ufName = "vevent";
        optimusName = "hcalendar";
    }
    else {
        ufName = this.testFixture.format.toLowerCase();
        optimusName = this.testFixture.format.toLowerCase();
    }


    if (Object.isArray(data[optimusName]))
        this.uf[ufName] = data[optimusName];
    else
        this.uf[ufName] = new Array(data[optimusName]);

    this.uf["parser-information"] = { "name": "Optimus" };

    this.run();

}

// Optimus
var optimus = new OptimusParserInterface();
optimus.name = "Optimus";
optimus.supportsFragment = false;
optimus.formats = ["hCalendar", "hCard", "hEntry", "hResume", "hReview", "hListing", "hAudio", "xFolkentry", "adr", "geo", "xfn", "votelinks", "rel-nofollow", "rel-tag", "rel-license"];
optimus.serviceUrl = "http://www.microformatique.com/optimus/?uri=[url]&format=json&filter=[format]";
microformatsTestRunner.addParserInterface(optimus);

//------------------------------------------------------------------------

// Inherits from ParserInterface
function hKitParserInterface() {
    var me;
    if (this.constructor == hKitParserInterface) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    ParserInterface(me);
}
Inherit(ParserInterface, hKitParserInterface);

// Override the base class function
hKitParserInterface.prototype.loadJSON = function (data) {

    this.ufRaw = data;
    var JSONtext = String(Object.toJSON(data));
    JSONtext = '{ "vcard": ' + JSONtext + ', "parser-information" : {"name": "hKit"}}';
    this.uf = JSONtext.evalJSON();
    this.run();
}

// hkit (Does not yet support p-json)
var hkit = new hKitParserInterface();
hkit.name = "hkit";
hkit.supportsPJSON = false;
hkit.supportsFragment = false;
hkit.formats = ["hCard"];
hkit.serviceUrl = "http://tools.microformatic.com/query/json/hkit/[url]";
microformatsTestRunner.addParserInterface(hkit);

//------------------------------------------------------------------------

// Inherits from ParserInterface
function SumoParserInterface() {
    var me;
    if (this.constructor == SumoParserInterface) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    ParserInterface(me);
}
Inherit(ParserInterface, SumoParserInterface);


// Override the base class function
SumoParserInterface.prototype.getJSON = function (testFixture, pageNumber) {
    this.testFixture = testFixture;
    this.pageNumber = pageNumber;
    var data = new Object();
    var ufExample = document.getElementById('uf');

    if (this.testFixture.format.toLowerCase() == "hcard") {
        var hcards = HCard.discover(ufExample);
        data = { "vcard": hcards, "parser-information": { "name": "Sumo"} }
    }
    if (this.testFixture.format.toLowerCase() == "hcalendar") {
        var hcards = HCalendar.discover(ufExample);
        data = { "vevent": hcards, "parser-information": { "name": "Sumo"} }
    }

    this.ufRaw = data;

    var JSONtext = String(Object.toJSON(data));
    JSONtext = JSONtext.replace(/List/g, "");
    // RegExp taken from mootools - string.hyphenate
    JSONtext = JSONtext.replace(/\w[A-Z]/g, function (A) { return (A.charAt(0) + "-" + A.charAt(1).toLowerCase()); });


    this.uf = JSONtext.evalJSON();
    this.run();
}


// Sumo
var sumo = new SumoParserInterface();
sumo.name = "Sumo";
sumo.formats = ["hCard", "hCalendar", "hreview"];
sumo.externalJavaScript.push(new Array("hcard", "http://ufxtract.com/testrunner/javascript/sumo/hcard.js"));
sumo.externalJavaScript.push(new Array("hcalendar", "http://ufxtract.com/testrunner/javascript/sumo/hcalendar.js"));
sumo.externalJavaScript.push(new Array("hreview", "http://ufxtract.com/testrunner/javascript/sumo/hreview.js"));

microformatsTestRunner.addParserInterface(sumo);


//------------------------------------------------------------------------

// Inherits from ParserInterface
function OperatorParserInterface() {
    var me;
    if (this.constructor == OperatorParserInterface) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    ParserInterface(me);
}
Inherit(ParserInterface, OperatorParserInterface);


// Operator contains a lot for Firefox only DOM/API calls
if(navigator.userAgent.indexOf('Firefox') > -1){

    // Override the base class function
    OperatorParserInterface.prototype.getJSON = function (testFixture, pageNumber) {
        this.testFixture = testFixture;
        this.pageNumber = pageNumber;
        this.ufRaw = new Object();

        var ufName = '';
        var ufClass = '';
        if (this.testFixture.format.toLowerCase() == "hcard") {
            ufName = "hCard";
            ufClass = 'vcard';
        }
        else if (this.testFixture.format.toLowerCase() == "hcalendar") {
            ufName = "hCalendar";
            ufClass = 'vevent';
        } else {
            ufName = this.testFixture.format.toLowerCase();
            ufClass = this.testFixture.format.toLowerCase();
        }


        // Simple hack to add Operator
        var ufExample = document.getElementById('uf');

        var ufs = Microformats.get(ufName, ufExample);

        var JSONtext = String(Object.toJSON(ufs));
        JSONtext = '{ "' + ufClass + '": ' + JSONtext + ', "parser-information" : {"name": "Operator"} }';
        this.uf = JSONtext.evalJSON();


        this.ufRaw = ufs;

        this.run();
    }


    // Operator
    var operator = new OperatorParserInterface();
    operator.name = "Operator ";
    operator.formats = ["hCard", "hCalendar", "hResume", "xfn", "geo", "adr", "hReview", "hAtom"]; // Add correct support
    operator.externalJavaScript.push(new Array("hresume", "http://ufxtract.com/testrunner/javascript/operator/hresume.js"));
    operator.externalJavaScript.push(new Array("xfn", "http://ufxtract.com/testrunner/javascript/operator/xfn.js"));
    operator.externalJavaScript.push(new Array("hreview", "http://ufxtract.com/testrunner/javascript/operator/hreview.js"));
    operator.externalJavaScript.push(new Array("hatom", "http://ufxtract.com/testrunner/javascript/operator/hatom.js"));

    microformatsTestRunner.addParserInterface(operator);
}




//------------------------------------------------------------------------

// Inherits from ParserInterface
function ShivParserInterface() {
    var me;
    if (this.constructor == ShivParserInterface) {
        me = this
    } else {
        me = arguments[arguments.length - 1]
    }
    ParserInterface(me);
}
Inherit(ParserInterface, ShivParserInterface);


// Override the base class function
ShivParserInterface.prototype.getJSON = function (testFixture, pageNumber) {
    this.testFixture = testFixture;
    this.pageNumber = pageNumber;
    this.ufRaw = new Object();

    var ufName = '';
    var ufClass = '';
    if (this.testFixture.format.toLowerCase() == "hcard") {
        ufName = "hCard";
        ufClass = 'vcard';
    }
    else if (this.testFixture.format.toLowerCase() == "hcalendar") {
        ufName = "hCalendar";
        ufClass = 'vevent';
    } else {
        ufName = this.testFixture.format.toLowerCase();
        ufClass = this.testFixture.format.toLowerCase();
    }



    var ufs = navigator.microformats.get(ufName, document.getElementById('uf'))

    var JSONtext = String(Object.toJSON(ufs[ufName]));
    JSONtext = '{ "' + ufClass + '": ' + JSONtext + ', "parser-information" : {"name": "Shiv"} }';
    this.uf = JSONtext.evalJSON();


    this.ufRaw = ufs;

    this.run();
}


// Microformats Shiv
var shiv = new ShivParserInterface();
shiv.name = "Shiv";
shiv.formats = ["hCard", "hCalendar", "geo", "adr"];

microformatsTestRunner.addParserInterface(shiv);


//------------------------------------------------------------------------

// Inherits from ParserInterface
/*
function CognitionParserInterface() {
var me;
if (this.constructor == CognitionParserInterface){
me = this
}else{
me = arguments[arguments.length-1]
}
ParserInterface(me);
}
Inherit(ParserInterface, CognitionParserInterface);

CognitionParserInterface.prototype.getJSON = function( testFixture, pageNumber ){
this.testFixture = testFixture;
this.pageNumber = pageNumber;
var thisDocument = '';
// Uses the proxy service to create fragment and PJSON support
thisDocument = escape(document.location.href);

this.ufUrl = this.proxyUrl + "?serviceurl=[serviceurl]&fragmentid=[fragmentid]&url=[url]&format=[format]&callback=[callback]&removehttp=true";
this.ufUrl = this.ufUrl.replace("[serviceurl]", escape(this.serviceUrl));
this.ufUrl = this.ufUrl.replace("[fragmentid]", 'uf');
this.ufUrl = this.ufUrl.replace("[url]", escape(thisDocument));
this.ufUrl = this.ufUrl.replace("[format]", "jcard");
this.ufUrl = this.ufUrl.replace("[callback]", this.parentObjectName + '.parsers[' + this.parserNumber + '].loadJSON');

this.addScript(this.ufUrl);
this.selected = false;	    	    
}

// Override the base class function
CognitionParserInterface.prototype.loadJSON = function( data ){
    
this.ufRaw = data;
var  JSONtext = String( Object.toJSON( data ) );
JSONtext = '{ "vcard": ' + JSONtext + '}';
this.uf = JSONtext.evalJSON();
this.run();
}

var cognition = new CognitionParserInterface();
cognition.name = "Cognition";
cognition.supportsPJSON = false;
cognition.supportsFragment = false;
cognition.formats = ["hCard"];
cognition.serviceUrl = "http://srv.buzzword.org.uk/jcard/[url]";
microformatsTestRunner.addParserInterface(cognition);

*/









// ISO Date 
// -----------------------------------------------------------------------------

function ISODate() {
    this.dY;
    this.dM = -1;
    this.dD = -1;
    this.z = false;
    this.tH;
    this.tM = -1;
    this.tS = -1;
    this.tD = -1;
    this.tzH;
    this.tzM = -1;
    this.tzPN = '+';
    this.z = false;
    this.format = 'W3C' // W3C or RFC3339

    if (arguments[0])
        this.Parse(arguments[0]);
}

ISODate.prototype.Parse = function (dateString) {

    var dateNormalised = '', parts;
    var datePart = '', timePart = '', timeZonePart = '';
    dateString = dateString.toUpperCase();


    // Break on 'T' divider or space
    if (dateString.indexOf('T') > -1) {
        parts = dateString.split('T');
        datePart = parts[0];
        timePart = parts[1];

        // Zulu UTC and time zone break
        if (timePart.indexOf('Z') > -1 || timePart.indexOf('+') > -1 || timePart.indexOf('-') > -1) {
            var tzArray = timePart.split('Z');
            timePart = tzArray[0];
            timeZonePart = tzArray[1];
            this.z = true;

            // Timezone
            if (timePart.indexOf('+') > -1 || timePart.indexOf('-') > -1) {
                var position = 0;
                if (timePart.indexOf('+') > -1)
                    position = timePart.indexOf('+');
                else
                    position = timePart.indexOf('-');

                timeZonePart = timePart.substring(position, timePart.length);
                timePart = timePart.substring(0, position);
            }
        }

    }
    else {
        datePart = dateString;
    }

    if (datePart != '') {
        this.ParseDate(datePart);
        if (timePart != '') {
            this.ParseTime(timePart);
            if (timeZonePart != '') {
                this.ParseTimeZone(timeZonePart);
            }
        }
    }
}


ISODate.prototype.ParseDate = function (dateString) {
    var dateNormalised = '', parts;
    // YYYY-MM-DD ie 2008-05-01 and YYYYMMDD ie 20080501
    parts = dateString.match(/(\d\d\d\d)?-?(\d\d)?-?(\d\d)?/);
    if (parts[1]) { this.dY = parts[1] };
    if (parts[2]) { this.dM = parts[2] };
    if (parts[3]) { this.dD = parts[3] };
}

ISODate.prototype.ParseTime = function (timeString) {
    var timeNormalised = '';
    // Finds timezone HH:MM:SS and HHMMSS  ie 13:30:45, 133045 and 13:30:45.0135
    var parts = timeString.match(/(\d\d)?:?(\d\d)?:?(\d\d)?.?([0-9]+)?/);
    timeSegment = timeString;
    if (parts[1]) { this.tH = parts[1] };
    if (parts[2]) { this.tM = parts[2] };
    if (parts[3]) { this.tS = parts[3] };
    if (parts[4]) { this.tD = parts[4] };
}

ISODate.prototype.ParseTimeZone = function (timeString) {
    var timeNormalised = '';
    // Finds timezone +HH:MM and +HHMM  ie +13:30 and +1330
    var parts = timeString.match(/([-+]{1})?(\d\d)?:?(\d\d)?/);
    if (parts[1]) { this.tzPN = parts[1] };
    if (parts[2]) { this.tzH = parts[2] };
    if (parts[3]) { this.tzM = parts[3] };
}

// Returns datetime in W3C Note datetime profile or RFC 3339 profile
ISODate.prototype.toString = function () {
    if (this.format == 'W3C') {
        dsep = '-';
        tsep = ':';
    }
    if (this.format == 'RFC3339') {
        dsep = '';
        tsep = '';
    }

    var output = '';
    if (typeof (this.dY) != 'undefined') {
        output = this.dY;
        if (this.dM > 0 && this.dM < 13) {
            output += dsep + this.dM;
            if (this.dD > 0 && this.dD < 32) {
                output += dsep + this.dD;
                // Time and can only be created with a full date
                if (typeof (this.tH) != 'undefined') {
                    if (this.tH > -1 && this.tH < 25) {
                        output += 'T' + this.tH
                        if (this.tM > -1 && this.tM < 61) {
                            output += tsep + this.tM;
                            if (this.tS > -1 && this.tS < 61) {
                                output += tsep + this.tS;
                                if (this.tD > -1)
                                    output += '.' + this.tD;
                            }
                        }
                        // Time zone offset can only be created with a hour
                        if (this.z) { output += 'Z' };
                        if (typeof (this.tzH) != 'undefined') {
                            if (this.tzH > -1 && this.tzH < 25) {
                                output += this.tzPN + this.tzH
                                if (this.tzM > -1 && this.tzM < 61)
                                    output += tsep + this.tzM;
                            }
                        }
                    }
                }
            }
        }
    }
    return output;
}




function CompareISODates(date1, date2) {
    var iso1 = new ISODate(date1).toString().replace(/:00/g, "");
    var iso2 = new ISODate(date2).toString().replace(/:00/g, "");
    if ((iso1 == '') && (iso2 == '')) {
        return false;
    } else {
        if (iso1 == iso2)
            return true;
        else
            return false;
    }
}




function PhoneNumber() {
    this.raw = "";
    this.canonicalisation = "";
    if (arguments[0])
        this.Parse(arguments[0]);
}

PhoneNumber.prototype.Reset = function () {
    this.raw = "";
    this.canonicalisation = "";
}



PhoneNumber.prototype.Parse = function (text) {
    this.Reset();
    this.raw = text;
    text = text.replace(" ", "");
    var parts
    parts = text.match(/[0-9\+]/g);
    if (Object.isArray(parts)) {
        if (Object.isArray(parts)) {
            for (var i = 0; i < parts.length; i++) {
                this.canonicalisation += parts[i];
            }
        }
    }
}

PhoneNumber.prototype.toString = function () {
    return this.canonicalisation;
}

function ComparePhoneNumbers(phoneNumber1, phoneNumber2) {
    var test1 = new PhoneNumber(phoneNumber1).toString();
    var test2 = new PhoneNumber(phoneNumber2).toString();
    if ((test1 == '') && (test2 == '')) {
        return false;
    } else {
        if (test1 == test2)
            return true;
        else
            return false;
    }
}



function RFC2426Geo() {

    this.latitude = 0;
    this.longitude = 0;

    if (arguments[0])
        this.Parse(arguments[0]);
}

RFC2426Geo.prototype.Parse = function (geo) {

    this.Reset();
    if (geo.indexOf(";") > 0) {
        parts = geo.split(';');

        // Make sure no more 6 decimal places
        this.latitude = parseFloat(parts[0]).toFixed(6);
        this.longitude = parseFloat(parts[1]).toFixed(6);

        // Remove trailing zero's
        this.latitude = parseFloat(this.FormatNumber(String(this.latitude)));
        this.longitude = parseFloat(this.FormatNumber(String(this.longitude)));

        if (this.latitude > 90 || this.latitude < -90)
            this.latitude = 0;

        if (this.longitude > 180 || this.longitude < -180)
            this.longitude = 0;

    }
}

RFC2426Geo.prototype.Reset = function () {
    this.latitude = 0;
    this.longitude = 0;
}

// Returns location in RFC2426 geo format
RFC2426Geo.prototype.toString = function () {
    return this.latitude + ";" + this.longitude;
}

RFC2426Geo.prototype.FormatNumber = function (geo) {

    var decPos = geo.indexOf(".")
    if (decPos > -1) {
        first = geo.substring(0, decPos);
        second = geo.substring(decPos, geo.length);

        while (second.charAt(second.length - 1) == "0")
            second = second.substring(0, second.length - 1);

        if (second.length > 1)
            return first + second;
        else
            return first;

    }
    return geo;
}

function CompareRFC2426Geo(geo1, geo2) {
    var converted1 = new RFC2426Geo(geo1).toString();
    var converted2 = new RFC2426Geo(geo2).toString();
    if ((converted1 == '') && (converted2 == '')) {
        return false;
    } else {
        if (converted1 == converted2)
            return true;
        else
            return false;
    }
}







