Problem/Motivation

When we add two adjacent tags using HTML source editing mode (General HTML Support (“GHS”) feature)
ckeditor5 merges those spans together.

Steps to reproduce

Try following code
<span> <span>aaaa</span> <span>bbbb</span> </span>

1. Copy above code and paste into editor with source mode.
2. Turn off source editing mode.
3. Turn ON source editing again and see above html changes.

There are couple of upstream issues for this:

Issue fork drupal-3389707

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

sahal_va created an issue. See original summary.

wim leers’s picture

Title: The nested span element is broken on ckeditor 5 » [upstream] CKEditor 5 merges nested and adjacent <span>s
Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs upstream bugfix

That is quite the exotic use case!

What is the purpose of such markup?

jvogt’s picture

I wouldn't call it exotic for us. We're trying to write a ckeditor5 plugin that outputs a themed "button". The HTML structure is as follows:

<a href="https://example.com" class="btn btn-lg arrow purple">
  <span>Button text</span>
  <span class="arrow-box"><span class="arrow"></span></span>
</a>

ckeditor5 rewrites that to the following which doesn't work with the theme:

<a href="https://example.com" class="btn btn-lg arrow purple">
  <span>Button text</span>
  <span class="arrow-box arrow"></span>
</a>

The theme CSS and HTML are provided by our institution and aren't Drupal-specific. We'd like to stick to the main theme as much as possible for the sake of maintenance and future compatibility, rather than reworking it to avoid nested spans. Also, from what I can tell, nesting a <span> in another <span> is considered valid HTML so it seems reasonable for ckeditor to allow it.

maddentim made their first commit to this issue’s fork.

maddentim’s picture

I have a similar situation with a site now running Drupal 10.2.1 with ckeditor4 still. The blocker to going to ckeditor5 is that they have existing buttons like this:

<p>
	<span class="button-wrapper-two-col">
		<span class="button-wrapper">
			<a href="https://new-philanthropists.org" target="_blank">Learn About NGAAP</a>
		</span>
		<span class="button-wrapper">
			<a href="/donate/new_generation_african_american_philanthropists">Join NGAAP</a>
		</span>
	</span>
</p>

the outer wrapper is just to make two buttons go side by side on desktop.
When this code is opened in ckeditor 5, it is transformed into:

<p>
  <a href="https://new-philanthropists.org" target="_blank">
		<span class="button-wrapper-two-col button-wrapper">Learn About NGAAP</span>
	</a>
	<span class="button-wrapper-two-col"> </span>
	<a href="/donate/new_generation_african_american_philanthropists">
		<span class="button-wrapper-two-col button-wrapper">Join NGAAP</span>
	</a>
</p>

This rearranging of the code breaks our button styling. I am open to changing our button CSS, but I don't see how I can make buttons with the current output. I tried turning off the "Correct faulty and chopped off HTML" to no avail.

Recently, I have been looking for a way to alter the input sent to the editor such that we could do some regex magic to find the buttons and rewrite the code so it works. My only solution that I see so far would be to set up a hook_form_alter function, look for the form_ids we need, then look for the fields and rewrite them. We have about 25 ckeditor fields so that could become a pretty hairy function. Open to suggestions.

wim leers’s picture

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

wim leers’s picture

Issue summary: View changes

https://github.com/ckeditor/ckeditor5/issues/15408 is the new issue to track apparently 🤷‍♂️

maddentim’s picture

Just circling back here. We could not wait for a real fix from upstream so we opted to develop an alter hook that uses DOMDocument() to restructure the HTML prior to sending it into CK5. While our hook is very specific to our quirky button structure, maybe someone can use parts to fix their own issues!

/**
 * Alters the existing ckeditor content before loading to tranform the buttons
 * into a compatible structure for ckeditor5.
 *
 * @param array $element
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 * @param array $context
 */
function MYMODULE_field_widget_single_element_form_alter(&$element, FormStateInterface $form_state, $context) {
  $formats = ['full_html', 'full_html_w_o_correction', 'basic_html'];
  if (key_exists('#format', $element) && in_array($element['#format'],$formats)) {
    $element['#default_value'] = _transformHtml($element['#default_value']);
  }
}

function _transformHtml($htmlFragment) {
  $doc = new DOMDocument();
  libxml_use_internal_errors(true);
  $doc->loadHTML('<div>' . mb_convert_encoding($htmlFragment, 'HTML-ENTITIES', 'UTF-8') . '</div>', LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
  libxml_clear_errors();

  $dummyRoot = $doc->getElementsByTagName('div')->item(0);

  $xpath = new DOMXPath($doc);

  /** Transform two cols buttons */
  $buttonWrapperTwoCols = $xpath->query(".//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper-two-col ')]", $dummyRoot);
  foreach ($buttonWrapperTwoCols as $span) {
      $div = $doc->createElement('div');
      $div->setAttribute('class', $span->getAttribute('class'));

      foreach ($span->getElementsByTagName('span') as $innerSpan) {
          foreach ($innerSpan->getElementsByTagName('a') as $a) {
              $newA = $a->cloneNode(true);
              $newA->setAttribute('class', 'btn');
              $div->appendChild($newA);
          }
      }

      $span->parentNode->replaceChild($div, $span);
  }

  /** Transform standalone buttons */
  // De-nest any spans
  $nestedButtonWrappers = $xpath->query(".//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper ')]//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper-two-col ')]");
  foreach ($nestedButtonWrappers as $nestedSpan) {
    $parentSpan = $nestedSpan->parentNode;
    if ($parentSpan && $nestedSpan !== $parentSpan) {
      while ($nestedSpan->firstChild) {
        $child = $nestedSpan->firstChild;
        if ($child->nodeName === 'a') {
            $parentSpan->insertBefore($child, $nestedSpan);
        } else {
            $nestedSpan->removeChild($child);
        }
      }
      $parentSpan->removeChild($nestedSpan);
    }
  }

  // Process all 'button-wrapper' spans for transformation
  $buttonWrappers = $xpath->query(".//span[contains(concat(' ', normalize-space(@class), ' '), ' button-wrapper ')]", $dummyRoot);
  foreach ($buttonWrappers as $span) {
    if ($span->parentNode) {
      $aElements = iterator_to_array($span->getElementsByTagName('a'));
      foreach ($aElements as $a) {
        $newA = $a->cloneNode(true);
        $newA->setAttribute('class', 'btn');

        if (!empty($span->parentNode)) {
          $span->parentNode->replaceChild($newA, $span);
        }
        else {
          $dummyRoot->appendChild($newA);
        }
      }
    }
  }

  $transformedHtml = '';
  foreach ($dummyRoot->childNodes as $child) {
    $transformedHtml .= $doc->saveHTML($child);
  }

  return $transformedHtml;
}

wim leers’s picture

Thanks, @maddentim! I do hope it'll help somebody else in the short term :)

marianojp’s picture

Nested span tag should be allowed. I'm also having this issue.

marianojp’s picture

Hi All, I found a temporary solution. I created another text format - /admin/config/content/formats. I did not use ckeditor 5.

smustgrave’s picture

Can this one be closed?

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.