2014-01-10 133 views
1

我试图从Javascript中的UL元素构建JSON对象。将无序列表转换为JSON

我有这样的表结构:

<ul id="trainingmenu"> 
    <li class="tmnode"><span id="M5lx9n" class="tmname">menu 1</span> 
     <ul> 
      <li class="tmnode"><span id="M66p48" class="tmname">menu 2</span> 
       <ul> 
        <li class="tmnode"><span class="tmname" id="Mschr">menu 3</span> 
        </li> 
       </ul> 
      </li> 
      <li class="tmnode"><span id="Mu03e0" class="tmname">menu 4</span></li> 
     </ul> 
    </li> 
</ul> 

这是JS:

var treeObject = {}; 

    function treeHTML(element, object) { 
     if (element.tagName=="UL") { element=element.firstChild; } 
     object["id"] = element.firstChild.id; 
     object["name"] = element.firstChild.innerHTML; 
     var nodeList = element.getElementsByTagName('LI'); 
     if (nodeList[0]) { 
      object["nodes"] = []; 
      for (var i = 0; i < nodeList.length; i++) { 
       object["nodes"].push({}); 
       treeHTML(nodeList[i], object["nodes"][object["nodes"].length - 1]); 
      } 
     } 
    } 
    treeHTML(document.getElementById("trainingmenu"), treeObject); 
    alert(JSON.stringify(treeObject)); 

..和这是我得到的输出:

{"id":"M5lx9n","name":"menu 1","nodes":[{"id":"M66p48","name":"menu 2","nodes":[{"id":"Mschr","name":"menu 3"}]},{"id":"Mschr","name":"menu 3"},{"id":"Mu03e0","name":"menu 4"}]} 

这部分这是我想要的格式 - 从每个LI元素的childNode中挑选id和name字段。但它是重复的节点 - 菜单3出现了两次

这里有一个fiddle

我可能搞砸了递归,但它已经盯着2天都没有制定出如何。谁能帮忙?

+1

FWIW,我微微一愣的代码在所有工作。如果HTML代码确实如此写入,则外部'ul'上的'element.firstChild' _should_返回'ul'和'li'之间的空白文本节点。在较新的浏览器中,您可以使用'.firstElementChild'来忽略文本节点。 – Alnitak

+0

正确 - 这只是为了便于阅读;我删除了小提琴的中断。但是,我不知道.firstElementChild(!),所以我在那里学到了一些东西。感谢那。 – WindsorAndy

+0

@WindsorAndy不要忘记'firstElementChild'是DOM规范中的一个适度增加 - 它可能不存在于旧版浏览器中。 – Alnitak

回答

1

由于Alnitak提到你应该使用.children.childNodes属性而不是.getElementsByTagName()方法。下面的代码片段使用Array原型的.filter()方法来过滤ul子代和.forEach()进行迭代。

function treeHTML(element) { 
    var o = {}; 
    o.id = element.firstElementChild.id; 
    o.name = element.firstElementChild.innerHTML; 
    o.nodes = []; 
    [].slice.call(element.children).filter(function(e) { 
     return e.tagName.toLowerCase() === 'ul'; 
    }).forEach(function(ul) { 
     [].slice.call(ul.children).forEach(function(li) { 
      o.nodes.push(treeHTML(li)); 
     }); 
    }); 
    return o;  
} 
var treeObj = treeHTML(document.getElementById("trainingmenu") 
           .firstElementChild); 

这里是一个替代的解决方案也应在旧版本的IE浏览器的工作:

function children(node, selector) { 
    var res = [], nodes = node.childNodes, l = nodes.length; 
    for (var i = 0; i < l; i++) { 
     if (nodes[i].nodeType === 1) { 
      if (selector && nodes[i].tagName.toLowerCase() === selector) { 
       res.push(nodes[i]); 
      } else if (!selector) { 
       res.push(nodes[i]); 
      } 
     } 
    } 
    return res; 
} 

function treeHTML(element) { 
    var o = {}; 
    var span = children(element, 'span'); 
    o.id = span[0].id; 
    o.name = span[0].innerHTML; 
    o.nodes = []; 
    var ul = children(element, 'ul'), n = ul.length; 
    for (var i = 0; i < n; i++) { 
     var li = children(ul[i]), l = li.length; 
     for (var j = 0; j < l; j++) { 
      o.nodes.push(treeHTML(li[j])); 
     } 
    } 

    return o;  
} 

var root = document.getElementById("trainingmenu"); 
var treeObj = treeHTML(children(root)[0]); 

http://jsfiddle.net/XZ54G/

+0

一个非常优雅的解决方案,BlackSheep。这个问题正在变成一个JS大师班。 – WindsorAndy

+0

@WindsorAndy谢谢,我很高兴它可以帮助。 – undefined

+0

您可以填充ES5阵列功能,但不能填充firstElementChild和children。 – Alnitak

1

它是重复的节点,因为element.getElementsByTagName发现全部后代,而不仅仅是儿童。你可能想测试一下nodeList[i].parentNode == element

或者只是直接迭代所有元素的子节点(element.childNodeselement.children),然后使用tagName == 'li'来查找那些元素。在格式良好的HTML中,您只能在ul元素的子列表中找到(空白)文本节点和li元素。

+0

感谢那..听起来很有希望。测试它.. – WindsorAndy

+0

...和评论节点。 –

+0

@JanDvorak是的,这些也是! – Alnitak