//>>built define("dojox/html/format", ["dojo/_base/kernel", "./entities", "dojo/_base/array", "dojo/_base/window", "dojo/_base/sniff"], function(lang, Entities, ArrayUtil, Window, has) { var dhf = lang.getObject("dojox.html.format",true); dhf.prettyPrint = function(html/*String*/, indentBy /*Integer?*/, maxLineLength /*Integer?*/, map/*Array?*/, /*boolean*/ xhtml){ // summary: // Function for providing a 'pretty print' version of HTML content from // the provided string. It's nor perfect by any means, but it does // a 'reasonable job'. // html: String // The string of HTML to try and generate a 'pretty' formatting. // indentBy: Integer // Optional input for the number of spaces to use when indenting. // If not defined, zero, negative, or greater than 10, will just use tab // as the indent. // maxLineLength: Integer // Optional input for the number of characters a text line should use in // the document, including the indent if possible. // map: Array // Optional array of entity mapping characters to use when processing the // HTML Text content. By default it uses the default set used by the // dojox.html.entities.encode function. // xhtml: boolean // Optional parameter that declares that the returned HTML should try to be 'xhtml' compatible. // This means normally unclosed tags are terminated with /> instead of >. Example:
->
var content = []; var indentDepth = 0; var closeTags = []; var iTxt = "\t"; var textContent = ""; var inlineStyle = []; var i; // Compile regexps once for this call. var rgxp_fixIEAttrs = /[=]([^"']+?)(\s|>)/g; var rgxp_styleMatch = /style=("[^"]*"|'[^']*'|\S*)/gi; var rgxp_attrsMatch = /[\w-]+=("[^"]*"|'[^']*'|\S*)/gi; // Check to see if we want to use spaces for indent instead // of tab. if(indentBy && indentBy > 0 && indentBy < 10){ iTxt = ""; for(i = 0; i < indentBy; i++){ iTxt += " "; } } //Build the content outside of the editor so we can walk //via DOM and build a 'pretty' output. var contentDiv = Window.doc.createElement("div"); contentDiv.innerHTML = html; // Use the entity encode/decode functions, they cache on the map, // so it won't multiprocess a map. var encode = Entities.encode; var decode = Entities.decode; /** Define a bunch of formatters to format the output. **/ var isInlineFormat = function(tag){ // summary: // Function to determine if the current tag is an inline // element that does formatting, as we don't want to // break/indent around it, as it can screw up text. // tag: // The tag to examine switch(tag){ case "a": case "b": case "strong": case "s": case "strike": case "i": case "u": case "em": case "sup": case "sub": case "span": case "font": case "big": case "cite": case "q": case "small": return true; default: return false; } }; //Create less divs. var div = contentDiv.ownerDocument.createElement("div"); var outerHTML = function(node){ // summary: // Function to return the outer HTML of a node. // Yes, IE has a function like this, but using cloneNode // allows avoiding looking at any child nodes, because in this // case, we don't want them. var clone = node.cloneNode(false); div.appendChild(clone); var html = div.innerHTML; div.innerHTML = ""; return html; }; var sizeIndent = function(){ var i, txt = ""; for(i = 0; i < indentDepth; i++){ txt += iTxt; } return txt.length; } var indent = function(){ // summary: // Function to handle indent depth. var i; for(i = 0; i < indentDepth; i++){ content.push(iTxt); } }; var newline = function(){ // summary: // Function to handle newlining. content.push("\n"); }; var processTextNode = function(n){ // summary: // Function to process the text content for doc // insertion // n: // The text node to process. textContent += encode(n.nodeValue, map); }; var formatText = function(txt){ // summary: // Function for processing the text content encountered up to a // point and inserting it into the formatted document output. // txt: // The text to format. var i; var _iTxt; // Clean up any indention organization since we're going to rework it // anyway. var _lines = txt.split("\n"); for(i = 0; i < _lines.length; i++){ _lines[i] = lang.trim(_lines[i]); } txt = _lines.join(" "); txt = lang.trim(txt); if(txt !== ""){ var lines = []; if(maxLineLength && maxLineLength > 0){ var indentSize = sizeIndent(); var maxLine = maxLineLength; if(maxLineLength > indentSize){ maxLine -= indentSize; } while(txt){ if(txt.length > maxLineLength){ for(i = maxLine; (i > 0 && txt.charAt(i) !== " "); i--){ // Do nothing, we're just looking for a space to split at. } if(!i){ // Couldn't find a split going back, so go forward. for(i = maxLine; (i < txt.length && txt.charAt(i) !== " "); i++){ // Do nothing, we're just looking for a space to split at. } } var line = txt.substring(0, i); line = lang.trim(line); // Shift up the text string to the next chunk. txt = lang.trim(txt.substring((i == txt.length)?txt.length:i + 1, txt.length)); if(line){ _iTxt = ""; for(i = 0; i < indentDepth; i++){ _iTxt += iTxt; } line = _iTxt + line + "\n"; } lines.push(line); }else{ // Line is shorter than out desired length, so use it. // as/is _iTxt = ""; for(i = 0; i < indentDepth; i++){ _iTxt += iTxt; } txt = _iTxt + txt + "\n"; lines.push(txt); txt = null; } } return lines.join(""); }else{ _iTxt = ""; for(i = 0; i < indentDepth; i++){ _iTxt += iTxt; } txt = _iTxt + txt + "\n"; return txt; } }else{ return ""; } }; var processScriptText = function(txt){ // summary: // Function to clean up potential escapes in the script code. if(txt){ txt = txt.replace(/"/gi, "\""); txt = txt.replace(/>/gi, ">"); txt = txt.replace(/</gi, "<"); txt = txt.replace(/&/gi, "&"); } return txt; }; var formatScript = function(txt){ // summary: // Function to rudimentary formatting of script text. // Not perfect, but it helps get some level of organization // in there. // txt: // The script text to try to format a bit. if(txt){ txt = processScriptText(txt); var i, t, c, _iTxt; var indent = 0; var scriptLines = txt.split("\n"); var newLines = []; for (i = 0; i < scriptLines.length; i++){ var line = scriptLines[i]; var hasNewlines = (line.indexOf("\n") > -1); line = lang.trim(line); if(line){ var iLevel = indent; // Not all blank, so we need to process. for(c = 0; c < line.length; c++){ var ch = line.charAt(c); if(ch === "{"){ indent++; }else if(ch === "}"){ indent--; // We want to back up a bit before the // line is written. iLevel = indent; } } _iTxt = ""; for(t = 0; t < indentDepth + iLevel; t++){ _iTxt += iTxt; } newLines.push(_iTxt + line + "\n"); }else if(hasNewlines && i === 0){ // Just insert a newline for blank lines as // long as it's not the first newline (we // already inserted that in the openTag handler) newLines.push("\n"); } } // Okay, create the script text, hopefully reasonably // formatted. txt = newLines.join(""); } return txt; }; var openTag = function(node){ // summary: // Function to open a new tag for writing content. var name = node.nodeName.toLowerCase(); // Generate the outer node content (tag with attrs) var nText = lang.trim(outerHTML(node)); var tag = nText.substring(0, nText.indexOf(">") + 1); // Also thanks to IE, we need to check for quotes around // attributes and insert if missing. tag = tag.replace(rgxp_fixIEAttrs,'="$1"$2'); // And lastly, thanks IE for changing style casing and end // semi-colon and webkit adds spaces, so lets clean it up by // sorting, etc, while we're at it. tag = tag.replace(rgxp_styleMatch, function(match){ var sL = match.substring(0,6); var style = match.substring(6, match.length); var closure = style.charAt(0); style = lang.trim(style.substring(1,style.length -1)); style = style.split(";"); var trimmedStyles = []; ArrayUtil.forEach(style, function(s){ s = lang.trim(s); if(s){ // Lower case the style name, leave the value alone. Mainly a fixup for IE. s = s.substring(0, s.indexOf(":")).toLowerCase() + s.substring(s.indexOf(":"), s.length); trimmedStyles.push(s); } }); trimmedStyles = trimmedStyles.sort(); // Reassemble and return the styles in sorted order. style = trimmedStyles.join("; "); var ts = lang.trim(style); if(!ts || ts === ";"){ // Just remove any style attrs that are empty. return ""; }else{ style += ";"; return sL + closure + style + closure; } }); // Try and sort the attributes while we're at it. var attrs = []; tag = tag.replace(rgxp_attrsMatch, function(attr){ attrs.push(lang.trim(attr)); return ""; }); attrs = attrs.sort(); // Reassemble the tag with sorted attributes! tag = "<" + name; if(attrs.length){ tag += " " + attrs.join(" "); } // Determine closure status. If xhtml, // then close the tag properly as needed. if(nText.indexOf(""; }else{ if(xhtml){ tag += " />"; }else{ tag += ">"; } closeTags.push(false); } var inline = isInlineFormat(name); inlineStyle.push(inline); if(textContent && !inline){ // Process any text content we have that occurred // before the open tag of a non-inline. content.push(formatText(textContent)); textContent = ""; } // Determine if this has a closing tag or not! if(!inline){ indent(); content.push(tag); newline(); indentDepth++; }else{ textContent += tag; } }; var closeTag = function(){ // summary: // Function to close out a tag if necessary. var inline = inlineStyle.pop(); if(textContent && !inline){ // Process any text content we have that occurred // before the close tag. content.push(formatText(textContent)); textContent = ""; } var ct = closeTags.pop(); if(ct){ ct = ""; if(!inline){ indentDepth--; indent(); content.push(ct); newline(); }else{ textContent += ct; } }else{ indentDepth--; } }; var processCommentNode = function(n){ // summary: // Function to handle processing a comment node. // n: // The comment node to process. //Make sure contents aren't double-encoded. var commentText = decode(n.nodeValue, map); indent(); content.push(""); newline(); }; var processNode = function(node) { // summary: // Entrypoint for processing all the text! var children = node.childNodes; if(children){ var i; for(i = 0; i < children.length; i++){ var n = children[i]; if(n.nodeType === 1){ var tg = lang.trim(n.tagName.toLowerCase()); if(has("ie") && n.parentNode != node){ // IE is broken. DOMs are supposed to be a tree. // But in the case of malformed HTML, IE generates a graph // meaning one node ends up with multiple references // (multiple parents). This is totally wrong and invalid, but // such is what it is. We have to keep track and check for // this because otherwise the source output HTML will have dups. continue; } if(tg && tg.charAt(0) === "/"){ // IE oddity. Malformed HTML can put in odd tags like: // , . It treats a mismatched closure as a new // start tag. So, remove them. continue; }else{ //Process non-dup, seemingly wellformed elements! openTag(n); if(tg === "script"){ content.push(formatScript(n.innerHTML)); }else if(tg === "pre"){ var preTxt = n.innerHTML; if(has("mozilla")){ //Mozilla screws this up, so fix it up. preTxt = preTxt.replace("
", "\n"); preTxt = preTxt.replace("
", "");
									preTxt = preTxt.replace("
", ""); } // Add ending newline, if needed. if(preTxt.charAt(preTxt.length - 1) !== "\n"){ preTxt += "\n"; } content.push(preTxt); }else{ processNode(n); } closeTag(); } }else if(n.nodeType === 3 || n.nodeType === 4){ processTextNode(n); }else if(n.nodeType === 8){ processCommentNode(n); } } } }; //Okay, finally process the input string. processNode(contentDiv); if(textContent){ // Insert any trailing text. See: #10854 content.push(formatText(textContent)); textContent = ""; } return content.join(""); //String }; return dhf; });