最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How can I "merge" two HTML tags via JavaScript - Stack Overflow

programmeradmin3浏览0评论

Here is my HTML "sandbox":

<p>1 2 3</p>
<span id="myid">
<span id="mytext">
<p>4 5 6</p>
<p>7 8 9</p>
<p>10 11 12</p>
</span>
</span>
<p>13 14 15</p>

I would like to "merge" the p tags accordingly:
(To make it easier to see I've added a minus sign where I've made the changes:)

<p>1 2 3 -
<span id="myid">
<span id="mytext">
- 4 5 6</p>
<p>7 8 9</p>
<p>10 11 12 -
</span>
</span>
- 13 14 15</p>

Every place I've added a minus sign, I have actually stripped a <p> or </p> tag making a merge between the two paragraph elements ( between 1 2 3 and 4 5 6 & between 10 11 12 and 13 14 15) - (This merge is only to achieve a display inline for 123-456 & 101112-131415).

How can I achieve this result with 'raw' JavaScript (not libraries please).

Note: I've tried to create something but It took me 80 lines of code - I am sure there is a better way of doing this.

Edit: Jon Hanna mentioned in his ment I could use correct HTML like so (as the result of JavaScript):

    <p>1 2 3 <span class="myclass mytext">4 5 6</span></p>
<p class="myclass mytext">7 8 9</p>
<p><span class="myclass mytext"10 11 12</span> 13 14 15</p>

Here is my HTML "sandbox":

<p>1 2 3</p>
<span id="myid">
<span id="mytext">
<p>4 5 6</p>
<p>7 8 9</p>
<p>10 11 12</p>
</span>
</span>
<p>13 14 15</p>

I would like to "merge" the p tags accordingly:
(To make it easier to see I've added a minus sign where I've made the changes:)

<p>1 2 3 -
<span id="myid">
<span id="mytext">
- 4 5 6</p>
<p>7 8 9</p>
<p>10 11 12 -
</span>
</span>
- 13 14 15</p>

Every place I've added a minus sign, I have actually stripped a <p> or </p> tag making a merge between the two paragraph elements ( between 1 2 3 and 4 5 6 & between 10 11 12 and 13 14 15) - (This merge is only to achieve a display inline for 123-456 & 101112-131415).

How can I achieve this result with 'raw' JavaScript (not libraries please).

Note: I've tried to create something but It took me 80 lines of code - I am sure there is a better way of doing this.

Edit: Jon Hanna mentioned in his ment I could use correct HTML like so (as the result of JavaScript):

    <p>1 2 3 <span class="myclass mytext">4 5 6</span></p>
<p class="myclass mytext">7 8 9</p>
<p><span class="myclass mytext"10 11 12</span> 13 14 15</p>
Share Improve this question edited Aug 17, 2012 at 23:23 funerr asked Aug 17, 2012 at 22:52 funerrfunerr 8,18617 gold badges90 silver badges139 bronze badges 14
  • 4 Your <span>s would be invalidly nested; they would open and close across different <p>s... If that's a typo, please correct it so we know what you mean. – Peter-Paul van Gemerden Commented Aug 17, 2012 at 22:55
  • @PPvG, it is meant to be like that. (I know it isn't valid html but I just need to to function like I demonstrated) – funerr Commented Aug 17, 2012 at 22:58
  • 3 Then it's invalid HTML, which causes browsers to react unpredictably. This will make JavaScript difficult to apply, it will likely react unreliably. – David Thomas Commented Aug 17, 2012 at 22:59
  • 1 Strictly speaking even the HTML in the first example, because it nests block-level elements (the ps) inside inline-level elements (the spans). But anyway; if your HTML is not validly structured, your browser can never build a correct representation of it in the DOM. And that makes it really hard to manipulate using JavaScript. – Peter-Paul van Gemerden Commented Aug 17, 2012 at 23:02
  • 3 @agam360 But you can't work with it once you have it like that. Nothing will parse it correctly, you won't be able to do anything with it in a deterministic fashion. – Dave Newton Commented Aug 17, 2012 at 23:06
 |  Show 9 more ments

3 Answers 3

Reset to default 2

(Bear with me, I started writing this before your latest edit. I think it's still useful, though.)

I'm not sure what you want to achieve, but I can tell you that it's very hard to achieve exactly what you're asking for using JavaScript. This is mostly because the HTML would be invalid.

Let's first walk through why the HTML would be invalid. When the browser parses HTML, it builds a nested tree of DOM Nodes. Your first example could be represented like this:

  • <p>
    • 1 2 3
  • <span id="myid">
    • <span id="mytext">
      • <p>
        • 4 5 6
      • <p>
        • 7 8 9
      • <p>
        • 10 11 12
  • <p>
    • 13 14 15

Using JavaScript and the DOM, you can walk through this tree and manipulate it. You could bine the text node from various <p> tags and place them in a single <p> tag without problem. But you won't be able to create the structure of your second example. It's simply not shaped like a DOM tree, so you can't use the DOM to create that HTML.

The only way manipulate the HTML into the shape of your second example is through innerHTML, like Jon Hanna explains. The good news is that browsers will always correct invalid HTML into a valid DOM structure. The bad news is that each browser does this differently, so you never know what you end up with.

The only real solution is to rethink your HTML structure, like you've done in your latest edit.

The way to solve your original problem depends on how static your original HTML is. I'll give a crude example, for which I'll presume that:

  • there's always exactly 5 <p>'s
  • the second through fourth of the <p>'s are always inside the <span>'s, and the first and fifth always outside them

If those two conditions are met, you could use something like this:

var paragraphs = document.getElementsByTagName('p');
var newParagraphs = [];

function createElementWithText(tagname, text, classname) {
    var classname = classname || '';
    var element = document.createElement(tagname);
    element.className = classname;
    element.appendChild(document.createTextNode(text));
    return element;
}

newParagraphs[0] = createElementWithText('p', paragraphs[0].textContent);
newParagraphs[0].appendChild(createElementWithText('span', paragraphs[1].textContent, 'myclass mytext'));

newParagraphs[1] = createElementWithText('p', paragraphs[2].textContent, 'myclass mytext');

newParagraphs[2] = document.createElement('p');
newParagraphs[2].appendChild(createElementWithText('span', paragraphs[3].textContent, 'myclass mytext'));
newParagraphs[2].appendChild(document.createTextNode(paragraphs[4].textContent));

I've posted a working example on JSFiddle: jsfiddle/vSrLJ/

You'll have to do it with obtaining the source-code through innerHTML etc, manipulating the string and then outputting the string.

While the DOMs generally won't stop the fact that you incorrectly have <p> inside <span>, it's just not logically possible for them to represent the output you want, since there's no HalfWrittenPElement object.

Your requirement to deliberately break the rules is at odds with the object model being designed to help you, so hacking with strings is all you've got.

Edit:

Though that said, the browsers will all fix it in their internal model anyway, so it's still going to end up as well-formed (if not necessarily valid) HTML. It'll just be so according to implementation-defined corrections rather than any spec.

I haven't tested this, but you'll get the idea. You can do the debugging and crossbrowser fine-tuning yourself ;)

I'll find all the <p> tags and for each of them, check if its siblings are <span>, <span> and <p>. Then I'll put the <span>s and the text of the second <p> inside the first <p>.

var pTags = document.getElementsByTagName('p');  // get all <p>

for (var i = 0; i < pTags.length; i++) {         // for every one of them
   var a = pTags[i];                             // take the <p> (and call it `a`)

   var b = a.nextElementSibling();               // and the next tag (call it `b`)
   if (b == null) continue;

   var c = b.nextElementSibling();               // and the next tag (call it `c`)
   if (c == null) continue;

   var d = c.nextElementSibling();               // and the next tag (call it `d`)
   if (d == null) continue;

   if (b.tagName.toUpperCase() == 'SPAN' &&      //  if b is <span>
       c.tagName.toUpperCase() == 'SPAN' &&      // and c is <span>
       d.tagName.toUpperCase() == 'P') {         // and d is <p> again

         a.appendChild(b);                       // put b inside a
         a.appendChild(c);                       // put c inside a
         a.appendChild(d.firstChild.nodeValue);  // put text of d inside a
   }
}

Note that nextElementSibling method is not cross-browser, but you can emulate it by calling nextSibling until it is of the Element type.

Note also that by calling appendChild the element first gets removed from its original container, and only then it is appended. Which is what we want.

Quick edit:

As others have pointed out, you have invalid html, what I didn't notice at first. But I suppose you wanted to have the <span>s properly closed.

发布评论

评论列表(0)

  1. 暂无评论