From 08096932199eb48884f1cae32f9e6f7f776e9c63 Mon Sep 17 00:00:00 2001
From: Matroskeen <matroskeen@3426249.no-reply.drupal.org>
Date: Fri, 15 Apr 2022 13:30:28 +0000
Subject: [PATCH] Issue #3225457 by RichardGaunt: Add additional functionality
 to `dom_remove`...

---
 src/Plugin/migrate/process/DomRemove.php | 51 +++++++++++++++++++++---
 tests/src/Unit/process/DomRemoveTest.php | 30 ++++++++++++++
 2 files changed, 76 insertions(+), 5 deletions(-)

diff --git a/src/Plugin/migrate/process/DomRemove.php b/src/Plugin/migrate/process/DomRemove.php
index 42085998..11a61058 100644
--- a/src/Plugin/migrate/process/DomRemove.php
+++ b/src/Plugin/migrate/process/DomRemove.php
@@ -8,13 +8,17 @@ use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\Row;
 
 /**
- * Remove nodes from a DOMDocument object.
+ * Remove nodes / attributes of a node from a DOMDocument object.
  *
  * Configuration:
  * - selector: An XPath selector.
- * - limit: (optional) The maximum number of nodes to remove.
+ * - limit: (optional) The maximum number of nodes / attributes to remove.
+ * - mode: (optional) What to remove. Possible values:
+ *   - element: An element (default option).
+ *   - attribute: An element's attribute.
+ * - attribute: An attribute name (required if mode is attribute)
  *
- * Usage:
+ * Examples:
  *
  * @code
  * process:
@@ -33,7 +37,26 @@ use Drupal\migrate\Row;
  * @endcode
  *
  * This example will remove the first two <img> elements from the source text
- * (if there are that many).  Omit 'limit: 2' to remove all <img> elements.
+ * (if there are that many). Omit 'limit: 2' to remove all <img> elements.
+ *
+ * @code
+ * process:
+ *   bar:
+ *     -
+ *       plugin: dom
+ *       method: import
+ *       source: text_field
+ *     -
+ *       plugin: dom_remove
+ *       mode: attribute
+ *       selector: //*[@style]
+ *       attribute: style
+ *     -
+ *       plugin: dom
+ *       method: export
+ * @endcode
+ *
+ * This example will remove "style" attributes from all tags.
  *
  * @MigrateProcessPlugin(
  *   id = "dom_remove"
@@ -41,6 +64,17 @@ use Drupal\migrate\Row;
  */
 class DomRemove extends DomProcessBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->configuration['mode'] = $this->configuration['mode'] ?? 'element';
+    if ($this->configuration['mode'] === 'attribute' && !isset($this->configuration['attribute'])) {
+      throw new \InvalidArgumentException('The "attribute" must be set if "mode" is set to "attribute".');
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -56,7 +90,14 @@ class DomRemove extends DomProcessBase {
       $walking_dead[] = $node;
     }
     foreach ($walking_dead as $node) {
-      $node->parentNode->removeChild($node);
+      switch ($this->configuration['mode']) {
+        case 'attribute':
+          $node->removeAttribute($this->configuration['attribute']);
+          break;
+        case 'element':
+          $node->parentNode->removeChild($node);
+          break;
+      }
     }
 
     return $this->document;
diff --git a/tests/src/Unit/process/DomRemoveTest.php b/tests/src/Unit/process/DomRemoveTest.php
index 94b47338..46ce2c7d 100644
--- a/tests/src/Unit/process/DomRemoveTest.php
+++ b/tests/src/Unit/process/DomRemoveTest.php
@@ -34,6 +34,7 @@ final class DomRemoveTest extends MigrateProcessTestCase {
    */
   public function providerTestTransform(): array {
     $input_string = '<ul><li>Item 1</li><li>Item 2</li><li><ul><li>Item 3.1</li><li>Item 3.2</li></ul></li><li>Item 4</li><li>Item 5</li></ul>';
+    $attribute_input_string = '<div style="font-size:15px;"><a class="btn-lg" href="#" style="padding: 10px;">Button</a><p class="lead-paragraph">Testing</p></div>';
     $cases = [
       'any li, no limit' => [
         $input_string,
@@ -78,9 +79,38 @@ final class DomRemoveTest extends MigrateProcessTestCase {
         ['selector' => '//li[./ul]'],
         '<ul><li>Item 1</li><li>Item 2</li><li>Item 4</li><li>Item 5</li></ul>',
       ],
+      'attribute, no limit' => [
+        $attribute_input_string,
+        ['selector' => '//*[@style]', 'mode' => 'attribute', 'attribute' => 'style'],
+        '<div><a class="btn-lg" href="#">Button</a><p class="lead-paragraph">Testing</p></div>',
+      ],
+      'attribute, limit 1' => [
+        $attribute_input_string,
+        ['selector' => '//*[@style]', 'mode' => 'attribute', 'attribute' => 'style', 'limit' => 1],
+        '<div><a class="btn-lg" href="#" style="padding: 10px;">Button</a><p class="lead-paragraph">Testing</p></div>',
+      ],
+      'attribute in specific tag' => [
+        $attribute_input_string,
+        ['selector' => '//p[@class]', 'mode' => 'attribute', 'attribute' => 'class'],
+        '<div style="font-size:15px;"><a class="btn-lg" href="#" style="padding: 10px;">Button</a><p>Testing</p></div>',
+      ],
+      'attribute not found' => [
+        $attribute_input_string,
+        ['selector' => 'p[@class]', 'mode' => 'attribute', 'attribute' => 'data-test'],
+        $attribute_input_string,
+      ],
     ];
 
     return $cases;
   }
 
+  /**
+   * Tests running remove attribute without specifying an attribute to remove.
+   */
+  public function testMissingConfiguration(): void {
+    $this->expectException(\InvalidArgumentException::class);
+    $this->expectExceptionMessage('The "attribute" must be set if "mode" is set to "attribute".');
+    (new DomRemove(['selector' => 'p', 'mode' => 'attribute'], 'dom_remove', []));
+  }
+
 }
-- 
GitLab