var Dom = function() {

    /* Private properties */

    var _separator          =  "~";
    var _defaultNode        =  document.body;


    /* some useful shortcuts. Add your own shortcuts here. */
    var _shortProperties    =  {
        "class"     : "className",
        "for"       : "htmlFor",
        "longdesc"  : "longDesc",
        "text"      : "innerHTML",
        "url"       : "href"
    };


    /*
        Entities can't be written with 'createTextNode' method, so we need to
        convert all entities into unicode sequences. Add your own entities here.
     */
    var _entityUnicode      = {
        "&nbsp;"    : " ",
        "&quot;"    : "\u0022",
        "&agrave;"  : "\u00e0",
        "&ccedil;"  : "\u00e7",
        "&egrave;"  : "\u00e8",
        "&eacute;"  : "\u00e9",
        "&igrave;"  : "\u00ec",
        "&ograve;"  : "\u00f2",
        "&ugrave;"  : "\u00f9",
        "&laquo;"   : "\u00ab",
        "&raquo;"   : "\u00bb",
        "&copy;"    : "\u00a9"
    };


    /* For memoization of nodes */
    var _cache                  = { };

    var _getElements    = function(e) {
        return e.split(_separator)[0];
    };


    var _createEntityReference  = function(text) {
        return text.replace(/(&[^;]+?;)/g, function(entity) {
            return _entityUnicode[entity] || "<unknown entity>";
        });
    };

    var _$ = function(id) {
        if (typeof id !== 'string') return id;
        return (document.getElementById)
            ? document.getElementById(id)
            : document.all[id];
    };

    /* Public/Privileged methods */

    return {

        setSeparator    : function(s) {
            if (/^[^a-z]{1}$/i.test(s)) {
                _separator = s;
            }
        },


        remove          : function(node, complete) {
            var node = _$(node);
            if (node) {
                if (!!complete) {
                    node.parentNode.removeChild(node);
                };
                while (node.childNodes[0]) node.removeChild(node.childNodes[0]);
                return true;
            };
            return false;
        },


        create          : function(obj) {

            /* Create a DOM Fragment */
            this.df = document.createDocumentFragment();

            /* Return a recursive function */
            return (function(obj, node, self) {

                for (var p in obj) {

                    if (obj.hasOwnProperty(p)) {
                        var el = _getElements(p);

                        if (typeof obj[p] === 'object') {
                            if (!_cache[el]) {
                                _cache[el] = document.createElement(el);
                            };
                            var elementNode = _cache[el].cloneNode(false);
                            arguments.callee(obj[p], elementNode);
                            node.appendChild(elementNode);
                        }
                        else {
                            var attribute = (_shortProperties[el])? _shortProperties[el] : el;
                            switch(attribute) {

                                case    "innerHTML" :
                                    var textNode = document.createTextNode(_createEntityReference(obj[p]));
                                    node.appendChild(textNode);
                                    break;

                                default             : node[attribute] = obj[p];

                            }
                        }
                    }

                }; /* end [for..in] */

                if (self) return self; /* end 'for' loop at first recursion */

            })(obj, this.df, this);

        },


        append          : function(type, node) {

            _defaultNode = (node)
                ? _$(node)
                : _$(_defaultNode);

            if (_defaultNode) {
                switch(type.toLowerCase()) {

                    case "before"           :
                            var pNode = _defaultNode.parentNode;
                            return pNode.insertBefore(this.df, _defaultNode);
                            break;
                    case "after"            :
                            var pNode = _defaultNode.parentNode;
                            return pNode.insertBefore(this.df, _defaultNode.nextSibling);
                            break;

                    case "into"             :
                    case "inside"           :
                            return _defaultNode.appendChild(this.df);
                            break;

                    default : throw "Unknown 'type' argument in 'append' method.";
                }
            };

            return false;
        }
    }
};
