3

I am receiving HTML that is in various formats and I'm trying to standardize the code using jQuery. In some cases I receive it using breaks <br> to separate lines. I need these to be <div> (or <p>) tags instead. For instance the following code:

a<br>b<br>c

needs to be converted into

<div>a</div><div>b</div><div>c</div>

This is a simple case as the contents in a could be surrounded by all kinds of <font>, <span> and other formatting options, all of which need to be retained.

Here was my first attempt. It finds all the <br> tags in the entire document then accesses the parent's contents to get all the HTML around the <br>. If the contents length is 1 then <br> is the only element in there (meaning a blank line) so skip on to the next one. Otherwise look at each element, surround it with <div> (or <p>) and remove the <br> as it isn't needed any more. Variable wrapper is either "<div />" or "<p />" depending on the browser.

$("br").each(function() {
  // Find all the br tags' parent nodes
  var elements = $(this).parent().contents();
  if (elements.length > 1) {
    // If there is one element than it is br so skip on.
    elements.each(function() {
      // Loop through each child element of the parent 
      var nodeName = this.nodeName.toLowerCase();
      if (nodeName !== 'div' && nodeName !== 'p') {
      // Make sure each child that is not <br> is wrapped. Remove <br> tags.
        if (nodeName == 'br') {
          $(this).remove();
        } else {
          $(this).wrap(wrapper);
        }
      }
    });
  }
});

This doesn't work, though, as it adds <div> (or <p>) tags around every sub-element so something like <font>a</font>bc<br>de<b>f</b> ends up like

<div><font>a</font></div>
<div>bc</div>
<div>de</div>
<div><b>f</b></div>

when it really needs to be

<div><font>a</font>bc</div>
<div>de<b>f</b></div>

(I broke it up to be easier to read.)

If I was doing this in a text parser, I'd find the contents just like I did above for variable elements, add a <div> at the beginning and a </div> at the end, then replace each <br> with </div><div> (except when it is already <div><br></div>, at which point I'd skip it). I have no idea how to do this in jQuery/Javascript. though, especially regarding the replace <br> with </div><div> portion. (The first part is elements.wrap(wrapper).)

Any help on this approach or an alternative approach would be appreciated.

--- UPDATE ---

@Ian supplied must of the code that worked. I expanded on it just a little and thought I'd include it here. I have tested it in FF, Safari, Chrome and IE 9+.

$("br").each(function(idx, el) {
    var $el = $(el),
        $parent = $el.parent(),
        $contents = $parent.contents(),
        $cur, $set, i;

    $set = $();
        if ($contents.length > 1) {
        for (i = 0; i < $contents.length; i++) {
            $cur = $contents.eq(i);

            if ($cur.is("br")) {
                $set.wrapAll(wrapper);
                $cur.remove();
                $set = $();
            } else {
                $set = $set.add($cur);
            }
        }
        $set.wrapAll(wrapper);
    }
});
4

6 回答 6

6

我的建议是将文本拆分为<br>

var lines = content.split("<br>");

现在您可以将每一行包装在 a 中<div>

var newContent = "";
for(var i=0,l=lines.length;i<l;i++){
     newContent += "<div>"+lines[i]+"</div>";

}
于 2013-08-28T17:31:16.740 回答
3

您可以执行您描述的确切文本替换方法。

function changeBrToDiv(node) {
  var html = $(node).html();
  html = "<div>" + html.split("<br>").join("</div><div>") + "</div>";
  node.html(html);
}

您可以在 jsFiddle 上看到这一点

于 2013-08-28T17:32:43.167 回答
1

<br> tags don't contain the text, and the parent tag is possibly the exact same for multiple <br>s like here:

<div id="theOne">text<br>more text<br>even more text</div>

Doing $('br').each() and then $(this).parent() is gonna give you the <div id="theOne"> twice.

If you are OK with approaching the html as text you could do this (similar to Ibu but simpler):

var newContent = '<div>'+content.split('<br>').join('</div><div>')+'</div>';

If you wanna use node manipulation (you downvoted a similar answer) then you'll have to use the native Javascript .childNodes to access textnodes, jQuery doesn't let you do that as far as I know.

于 2013-08-28T17:35:06.200 回答
1

在我看来,您可能希望将br标签留在 DOM 中,直到您完成遍历它。div在您的第二个示例中,除非您将br标签作为参考点,否则您将不知道使用起始标签可以达到多高。我会抓住父元素,查找 DOM,直到找到开头<body>或 a <br>,然后在此处打上开头标签。冲洗并重复

于 2013-08-28T17:32:27.547 回答
0

你可以在 POJS 中尝试这样的事情;操纵 DOM 而不是将您的标记视为文本。您可能需要稍微修改它以适应您的确切要求。

HTML

<div>one
    <br>two</div>
<br>
<div>
    <br>
</div>
<div>
    <div>three
        <br><font>a</font>bc
        <br>de<b>f</b>

    </div>
</div>
<span>a</span>

<br>b<span>c</span>d
<br>e<span>f</span>g

Javascript

function prepend(referenceNode, newNode) {
    if (referenceNode.firstChild) {
        referenceNode.insertBefore(newNode, referenceNode.firstChild);
    } else {
        referenceNode.appendChild(newNode);
    }
}

var brs = document.getElementsByTagName("br"),
    docFragment = document.createDocumentFragment(),
    index1,
    index2,
    parent,
    siblings,
    sibling,
    br;

for (index1 = brs.length - 1; index1 >= 0; index1 -= 1) {
    div = document.createElement("div");
    br = brs[index1];
    parent = br.parentNode;
    siblings = parent.childNodes;
    for (index2 = siblings.length - 1; index2 >= 0; index2 -= 1) {
        sibling = siblings[index2];
        if (sibling === br) {
            break;
        }

        prepend(div, sibling);
    }

    prepend(docFragment, div);
    br.parentNode.removeChild(br);
    if (!brs.length && siblings.length) {
        div = document.createElement("div");
        for (index2 = siblings.length - 1; index2 >= 0; index2 -= 1) {
            prepend(div, siblings[index2]);
        }

        prepend(docFragment, div);
    }

    if (docFragment.childNodes.length && !siblings.length) {
        parent.appendChild(docFragment);
    }
}

jsfiddle

于 2013-08-28T20:26:36.793 回答
0

我认为这可能是你的问题:

var elements = $(this).parent().contents();

应该

var elements = $(this).parent().children();

因为 contents 也将文本元素作为节点返回,而 children 只会返回实际的 dom 元素

于 2013-08-28T17:31:55.467 回答