jQuery.fn.outerHTML = function(replacement) {
    if (!this.length) {
        return null;
    }
    if (replacement!==undefined) {
        $(this).replaceWith(replacement);
    }
    var tmpNode = $('<div></div>').append(this.eq(0).clone());
    var returnValue = tmpNode.html();
    tmpNode.remove(); //against memoryleaks
    return returnValue;
};

jQuery.fn.startTag = function() {
    if (!this.length) {
        return null;
    }
    var results = this.outerHTML().match(/^<.*?>/);
    return results[0];
};

function html(s)
{
    return (s+'').replace(/&/g,  '&amp;').replace(/\"/g, '&quot;').replace(/</g,  '&lt;').replace(/>/g,  '&gt;');
} //function html

jQuery.fn.columnize = function(options) {
    var currentBox;
    if (typeof options==='undefined') {
        options = {};
    }
    if (typeof options.columnCallback==='undefined') {
        throw new Error('no columnCallBack defined');
        options.columnCallback = function(currentBox, done) {};
    }
    if (typeof options.maxColumns==='undefined') {
        options.maxColumns = null;
    }
    if (typeof options.height==='undefined') {
        options.height = null;
    }
    $('body').append('<div id="temporaryhtmlforcolumnizer" style="display:none"></div>');
    var temporaryLayer = $('#temporaryhtmlforcolumnizer');
    this.each(function() {
        currentBox = $(this);
        var height = (options.height===null?currentBox.height():options.height);
        currentBox.css({'height':'auto'});
        var boxHtml = currentBox.html();
        if (currentBox.height()>height) { //if it fits we don't need to do anything
            temporaryLayer.html(boxHtml);
            currentBox.empty();
            var newHtml = '';
            var width = 0;

            var startNewColumn = function(path)
            {
                currentBox.html(newHtml);
                var returnValue = options.columnCallback(currentBox, false);
                currentBox = returnValue.currentBox;
                if (currentBox) { //it might have returned null to force us to stop
                    currentBox.css({'height':'auto'});
                    newHtml = '';
                    for (var i in path) {
                        if (path[i].nodeType==1) {
                            newHtml += $(path[i]).startTag();
                        }
                    }
                }
            }; //function startNewColumn

            var walkHtml = function(node, path)
            {
                var outer, currentNode, childPath, i;
                var closingTag = '';
                var closingPath = '';
                for (i in path) {
                    if (path[i].nodeType==1) {
                        closingPath = '</'+path[i].nodeName+'>'+closingPath;
                    }
                }
                if (path.length) {
                    newHtml += $(node).startTag();
                    closingTag = '</'+node.nodeName+'>';
                }
                for (i=0; i!=node.childNodes.length; ++i) {
                    currentNode = node.childNodes[i];
                    if (currentNode.nodeType == 3) { //text
                        outer = currentNode.data;
                    } else {
                        outer = $(currentNode).outerHTML();
                    }
                    currentBox.html(newHtml+outer+closingPath);
                    if (currentBox.height()<=height) {
                        //everything fits
                        newHtml += outer;
                    } else {
                        //START it does not fit
                        if (currentNode.nodeType == 3) { //TEXT
                            var tmpText = '';
                            var words = currentNode.data.split(/\s/);
                            var sentence = '';
                            for (var w in words) {
                                currentBox.html(newHtml+sentence+(sentence?' ':'')+words[w]+closingPath);
                                if (currentBox.height()>height) {
                                    newHtml += sentence + closingPath;
                                    startNewColumn(path);
                                    if (!currentBox) { //check if we should stop, this can be caused by startNewColumn
                                        break;
                                    }
                                    sentence = '';
                                }
                                sentence += (sentence?' ':'')+words[w];
                            }
                            if (!currentBox) { //check if we should stop, this can be caused by startNewColumn
                                break;
                            }
                            newHtml += sentence;
                        } else if (currentNode.nodeType == 1) { //TAG
                            if (currentNode.tagName==='SCRIPT' || currentNode.tagName==='STYLE') {
                                //do nothing
                            } else if (currentNode.tagName==='IMG' || currentNode.tagName==='TABLE' || ($(currentNode).css('display')==='block' && currentNode.tagName!='P')) {
                                if (newHtml) { //there already is html inside
                                    //it won't fit we already checked for that
                                    newHtml += closingPath+closingTag;
                                    startNewColumn(path);
                                    if (!currentBox) { //check if we should stop, this can be caused by startNewColumn
                                        break;
                                    }
                                } else { //the currentBox is still empty
                                    //if it doesn't fit in this column it won't fit into the next one either
                                    //so we don't start a new column
                                }
                                newHtml += outer;
                            } else {
                                path.push(currentNode);
                                walkHtml(currentNode, path);
                                path.pop();
                            }
                        } else {
                            throw new Error(childPath+'   UNKNOWN');
                        }
                        //END it does not fit
                    }
                }
                newHtml += closingTag;
            }; //function walkHtml

            walkHtml(temporaryLayer[0], []);
        }
        if (currentBox) {
            currentBox.html(newHtml);
            options.columnCallback(currentBox, true);
        }
    }); //each
    temporaryLayer.remove();
}; //jQuery.fn.columnize
