/**
 * 将一个html标记 转 json串
 * @param {String} html 要处理的html标签。html串必须有且仅有一个根节点。
 * @param {int} indent_str_count 起始缩进符个数
 * @param {String} indent_str 缩进符,默认tab键
 * @param {Object} indent 缩进对象
 * @return {String} json
 *  {
 *  "tag": "标签名",
 *  "attrs": {"属性名": "属性值", ...},
 *  "content": ["标签内容" | json  | [json数组]]
 *  }
 *         出错返回结果: {"error": "html串必须包括一个根节点"}
 * @test 嵌套(nested):html2json("<table><tr><td class=\"popupmenu_option\" onclick=\"Do('detail')\">详细结构</td></tr><tr><td class=\"popupmenu_option\" onclick=\"Do('rawdata')\">原始邮件</td><tr><td class=\"popupmenu_option\" onclick=\"Do('detail')\">详细结构</td></tr></tr></table>")
 * @test 同级(sibling):html2json("<tr><td class=\"popupmenu_option\" onclick=\"Do('detail')\">详细结构</td></tr><tr><td class=\"popupmenu_option\" onclick=\"Do('rawdata')\">原始邮件</td></tr>")
 * @create 堅 @ 2012-6-7 16:30:48
 * @modify [content字段的文本添加对"进行替换为\"的转义形式] by [堅] @ [2012-6-8 9:03:36]
 * @modify [content字段添加:对标签内容若仍为html标签,则继续转json] by [堅] @ [2012-6-8 9:22:32]
 * @modify [添加json串缩进显示] by [堅] @ [2012-6-8 11:54:27]
 * @modify [属性组(attrs)JSON生成时,按空格分离属性到数组(.split(/\s+/))的方式改为按正则匹配到数组(.match(/\w+=(?:"(?:[^"]|(?:\"))+?"|'(?:[^']|(?:\'))+?')/g);)的方式] by [堅] @ [2012年6月11日11:51:56]
 * @modify [添加对用户传入的html串做根/嵌套标签和同级标签的处理] by [堅] @ [2012-6-11 17:25:07]
 * @modify [log] by [user] @ [time]
 * @TODO: input img 这类单行标签未处理
 */
function html2json(html, indent_str_count, indent_str, indent) {
/// 常量设置
var HTML_TAG_NAME = "tag";
var HTML_TAG_ATTRIBUTES = "attrs";
var HTML_TAG_CONTENT = "content";
// HTML元素组
var htmlEls = null;
// 是否首次(非递归)执行。indent为空说明是首次调用
var isFirstRun = indent ? false : true;

/// 验证是否为字符串,若是去除其前后导空格
if (!html || typeof(html) != "string") {
return null;
}
String.prototype.trim = String.prototype.trim || 
function(){return this.replace(/^\s+|\s+$/g, "");};
html = html.trim();

/// 同级标签解析,若match后长度等于1,说明不是同级标签 */
htmlEls = html.match(/<(\w+)([^>]*)>(.*?)<\/\1>/gi);
if (!htmlEls) {
// 首次调用(非递归),即html不符合要求,直接返null;否则生成content字段的内容并返回
return isFirstRun ? null : "\"" + html.replace(/\"/g, "\\\"") + "\"";
}

/// 设置各种默认值
// 默认缩进1个字符
indent_str_count = indent_str_count || 1;
// 默认缩进字符为tab键
indent_str = indent_str || "\t";
// 缩进对象(共用)
indent = indent || {
"strings": [],
"str": indent_str,
 /**
 * 获取count个缩进字符
 * p.s. 写完发现这个貌似有点像传说中的享元模式,昨天刚看的,没想到无意中写出来了。
 *      于是果断把函数名由get改为了factory。
 *      strings数组里存放的就是共享的缩进单元,相同个数的缩进符 共享 同一个缩进单元。
 *      如,factory(5)都是 共享 strings[4]单元。
 * @param {int} count
 * @return {String} count个缩进字符
 * @test 
 * @create 堅 @ 2012-6-8 10:26:32
 * @modify [log] by [user] @ [time]
 */
"factory": function(count) {
  var index = count - 1;
  if (!this.strings[index]) {
  var string = new Array(count + 1).join(this.str);
  this.strings[index] = string;
  }
  return this.strings[index];
},
"getIndentStr": function() {
return this.str || "\t";;
}
};

if (htmlEls.length > 1) {
/// html串内含多个同级标签
if (isFirstRun) {
// 用户传进的html串为同级标签。
return {"error": "html串必须包括一个根节点"};
}
var elsJson = "["
for (var i = 0; ; i++) {
elsJson += html2json(htmlEls[i], indent_str_count + 1, indent.getIndentStr(), indent);
if (i >= htmlEls.length -1) {
elsJson += "\n";
break;
}
elsJson += ", ";
}
elsJson += indent.factory(indent_str_count - 1) + "]";
return elsJson;
} else if (htmlEls.length == 1) {
/// html串为一个根标签 或是 一组嵌套标签
// 根标签或嵌套标签解析
htmlEls = html.match(/^\s*<(\w+)([^>]*)>(.*)<\/\1>\s*$/gi);
var tag = RegExp.$1;
var content = RegExp.$3;
var attrs = RegExp.$2.trim().match(/\w+=(?:"(?:[^"]|(?:\"))+?"|'(?:[^']|(?:\'))+?')/g);
var json = "";

json = "{\n" +
indent.factory(indent_str_count) + "\"" + HTML_TAG_NAME + "\": \"" + tag + "\", \n" +
indent.factory(indent_str_count) + "\"" + HTML_TAG_ATTRIBUTES + "\": " + 
(function() {
var attrsJson = "\"\"";
if (attrs && attrs.length) {
attrsJson = "{\n";
for (var i = 0; ; i++) {
attrPair = attrs[i].split("=");
name = attrPair[0];
value = attrPair[1].replace(/^['"]?|['"]?$/g, "");
attrsJson += indent.factory(indent_str_count + 1) + 
"\"" + name + "\": \"" + value + "\"";
if (i >= attrs.length - 1) {
attrsJson += "\n";
break;
}
attrsJson += ", \n";
}
attrsJson += indent.factory(indent_str_count)+ "}";
}
return attrsJson;
})()  + ", \n" +
indent.factory(indent_str_count) + "\"" + HTML_TAG_CONTENT + "\": " + 
html2json(content, indent_str_count + 1, indent.getIndentStr(), indent) + "\n" + 
indent.factory(indent_str_count-1) + "}";
return json;
}
}