// HTML5 text/html .innerHTML getter function with some options

var force_lowercase_attr_names = true;
var force_lowercase_element_names = true;
var show_attrs = true;
var prefix_pre_textarea_listing_content_with_newline = true;
var show_simple_doctype = true;
var force_lowercase_doctype_name = false;
var solidus_for_void_elements = false;
var space_before_solidus = false;
var processing_instruction_as_comment = false;

function escapeTextNodeValue(data) {
    return data.replace(/(<)|(>)|(&)|(\u00A0)/g, function(match, left, right, amp) {
        if (left) return "&lt;";
        if (right) return "&gt;"
        if (amp) return "&amp;";
        return "&nbsp;"
    });
}

function escapeAttrNodeValue(value) {
    return value.replace(/(&)|(")|(\u00A0)/g, function(match, amp, quote) {
        if (amp) return "&amp;";
        if (quote) return "&quot;";
        return "&nbsp;";
    });
}

function isVoidElement(element) {
    return (/^area$|^base$|^basefont$|^bgsound$|^br$|^col$|^embed$|^frame$|^hr$|^img$|^input$|^link$|^meta$|^param$|^spacer$|^wbr$/i).test(element.tagName);
}

function isCDATA(node) {
    for (var p = node.parentNode; p; p = p.parentNode) {
        if ((/^style$|^script$|^xmp$|^iframe$|^noembed$|^noframes$|^noscript$|^plaintext$/i).test(p.tagName)) {
            return true;
        }
    }
    return false;
}

function needsNewlinePrefix(element) {
    return (/^pre$|^textarea$|^listing$/i).test(element.tagName);
}

function getAttrHTML(attr) {
    var markup = attr.name;
    if (force_lowercase_attr_names) {
        markup = markup.toLowerCase();
    }
    markup += '="';
    markup += escapeAttrNodeValue(attr.value);
    markup += '"';
    return markup;
}

function getInnerHTML(node) {
    var markup = "";
    if (node.hasChildNodes()) {
        var current = node.firstChild;
        for ( ; ; ) {
            switch (current.nodeType) {
                case 1:
                    markup += "<";
                    if (force_lowercase_element_names) {
                        markup += current.tagName.toLowerCase();
                    } else {
                        markup += current.tagName;
                    }
                    if (show_attrs) {
                        var attrs = current.attributes;
                        for (var z = 0; z < attrs.length; ++z) {
                            markup += " ";
                            markup += getAttrHTML(attrs[z]);
                        }
                    }
                    if (solidus_for_void_elements && isVoidElement(current)) {
                        if (space_before_solidus) {
                            markup += " ";
                        }
                        markup += "/";
                    }
                    markup += ">";
                    if (prefix_pre_textarea_listing_content_with_newline && needsNewlinePrefix(current)) {
                        markup += "\n";
                    }
                    break;
                case 3: case 4:
                    markup += isCDATA(current) ? current.data : escapeTextNodeValue(current.data);
                    break;
                case 7:
                    if (processing_instruction_as_comment) {
                        markup += "<!--?";
                    } else {
                        markup += "<?";
                    }
                    markup += current.target;
                    markup += current.data;
                    // text/html PIs don't have the ending ?
                    if (processing_instruction_as_comment) {
                        markup += "-->";
                    } else {
                        markup += ">";
                    }
                    break;
                case 8:
                    markup += "<!--";
                    markup += current.data;
                    markup += "-->";
                    break;
                case 10:
                    markup += "<!DOCTYPE ";
                    if (force_lowercase_doctype_name) {
                        markup += current.name.toLowerCase();
                    } else {
                        markup += current.name;
                    }
                    if (!show_simple_doctype) {
                        if (current.publicId) {
                            markup += " PUBLIC \"";
                            markup += current.publicId;
                            markup += "\"";
                        }
                        if (current.systemId) {
                            markup += " \"";
                            markup += current.systemId;
                            markup += "\"";
                        }
                    }
                    markup += ">";
                    break;
                default:
                    throw "INVALID_STATE_ERR";
            }
            if (current.firstChild) {
                current = current.firstChild;
            } else {
                var done = false;
                for ( ; ; ) {
                    if (current.nodeType == 1 && !isVoidElement(current)) {
                        markup += "</";
                        if (force_lowercase_element_names) {
                            markup += current.tagName.toLowerCase();
                        } else {
                            markup += current.tagName;
                        }
                        markup += ">";
                    }
                    if (current === node.lastChild) {
                        done = true;
                        break;
                    } else if (current.nextSibling) {
                        current = current.nextSibling;
                        break;
                    } else {
                        current = current.parentNode;
                    }
                }
                if (done) {
                    break;
                }
            }
        }
    }
    return markup;
}

function getOuterHTML(current) {
    if (current instanceof Element == false) {
        throw "INVALID_STATE_ERROR";
    }
    var markup = "<";
    if (force_lowercase_element_names) {
        markup += current.tagName.toLowerCase();
    } else {
        markup += current.tagName;
    }
    if (show_attrs) {
        var attrs = current.attributes;
        for (var z = 0; z < attrs.length; ++z) {
            markup += " ";
            markup += getAttrHTML(attrs[z]);
        }
    }
    if (solidus_for_void_elements && isVoidElement(current)) {
        if (space_before_solidus) {
            markup += " ";
        }
        markup += "/";
    }
    markup += ">";
    if (prefix_pre_textarea_listing_content_with_newline && needsNewlinePrefix(current)) {
        markup += "\n";
    }
    if (current.hasChildNodes()) {
        markup += getInnerHTML(current);
    } else {
        if (!isVoidElement(current)) {
            markup += "</";
            if (force_lowercase_element_names) {
                markup += current.tagName.toLowerCase();
            } else {
                markup += current.tagName;
            }
            markup += ">";
        }
    }
    return markup;
}
