diff --git a/src/Plugin/migrate_plus/data_parser/SimpleXml.php b/src/Plugin/migrate_plus/data_parser/SimpleXml.php
new file mode 100644
index 0000000000000000000000000000000000000000..a526ecc2346155495472105b221df31769427629
--- /dev/null
+++ b/src/Plugin/migrate_plus/data_parser/SimpleXml.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\migrate_plus\Plugin\migrate_plus\data_parser;
+
+use Drupal\migrate\MigrateException;
+use Drupal\migrate_plus\DataParserPluginBase;
+
+/**
+ * Obtain XML data for migration using the SimpleXML API.
+ *
+ * @DataParser(
+ *   id = "simple_xml",
+ *   title = @Translation("Simple XML")
+ * )
+ */
+class SimpleXml extends DataParserPluginBase {
+
+  use XmlTrait;
+
+  /**
+   * Array of matches from item_selector.
+   *
+   * @var array
+   */
+  protected $matches = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    // Suppress errors during parsing, so we can pick them up after.
+    libxml_use_internal_errors(TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function openSourceUrl($url) {
+    $xml_data = $this->getDataFetcherPlugin()->getResponseContent($url);
+    $xml = simplexml_load_string($xml_data);
+    $this->registerNamespaces($xml);
+    $xpath = $this->configuration['item_selector'];
+    $this->matches = $xml->xpath($xpath);
+    foreach (libxml_get_errors() as $error) {
+      $error_string = self::parseLibXmlError($error);
+      throw new MigrateException($error_string);
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function fetchNextRow() {
+    $target_element = array_shift($this->matches);
+
+    // If we've found the desired element, populate the currentItem and
+    // currentId with its data.
+    if ($target_element !== FALSE && !is_null($target_element)) {
+      foreach ($this->fieldSelectors() as $field_name => $xpath) {
+        foreach ($target_element->xpath($xpath) as $value) {
+          $this->currentItem[$field_name][] = (string) $value;
+        }
+      }
+      // Reduce single-value results to scalars.
+      foreach ($this->currentItem as $field_name => $values) {
+        if (count($values) == 1) {
+          $this->currentItem[$field_name] = reset($values);
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/Plugin/migrate_plus/data_parser/Xml.php b/src/Plugin/migrate_plus/data_parser/Xml.php
index e552cb08e321fd54d416ad31903034127aed2bef..4592f7814b9ee112ae91d7d8db53ffed770fbfdb 100644
--- a/src/Plugin/migrate_plus/data_parser/Xml.php
+++ b/src/Plugin/migrate_plus/data_parser/Xml.php
@@ -2,19 +2,20 @@
 
 namespace Drupal\migrate_plus\Plugin\migrate_plus\data_parser;
 
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\migrate\MigrateException;
 use Drupal\migrate_plus\DataParserPluginBase;
 
 /**
- * Obtain XML data for migration.
+ * Obtain XML data for migration using the XMLReader pull parser.
  *
  * @DataParser(
  *   id = "xml",
  *   title = @Translation("XML")
  * )
  */
-class Xml extends DataParserPluginBase implements ContainerFactoryPluginInterface {
+class Xml extends DataParserPluginBase {
+
+  use XmlTrait;
 
   /**
    * The XMLReader we are encapsulating.
@@ -293,59 +294,4 @@ class Xml extends DataParserPluginBase implements ContainerFactoryPluginInterfac
     }
   }
 
-  /**
-   * Registers the iterator's namespaces to a SimpleXMLElement.
-   *
-   * @param \SimpleXMLElement $xml
-   *   The element to apply namespace registrations to.
-   */
-  protected function registerNamespaces(\SimpleXMLElement $xml) {
-    if (isset($this->configuration['namespaces']) && is_array($this->configuration['namespaces'])) {
-      foreach ($this->configuration['namespaces'] as $prefix => $ns) {
-        $xml->registerXPathNamespace($prefix, $ns);
-      }
-    }
-  }
-
-  /**
-   * Parses a LibXMLError to a error message string.
-   *
-   * @param \LibXMLError $error
-   *   Error thrown by the XML.
-   *
-   * @return string
-   *   Error message
-   */
-  public static function parseLibXmlError(\LibXMLError $error) {
-    $error_code_name = 'Unknown Error';
-    switch ($error->level) {
-      case LIBXML_ERR_WARNING:
-        $error_code_name = t('Warning');
-        break;
-
-      case LIBXML_ERR_ERROR:
-        $error_code_name = t('Error');
-        break;
-
-      case LIBXML_ERR_FATAL:
-        $error_code_name = t('Fatal Error');
-        break;
-    }
-
-    return t(
-      "@libxmlerrorcodename @libxmlerrorcode: @libxmlerrormessage\n" .
-      "Line: @libxmlerrorline\n" .
-      "Column: @libxmlerrorcolumn\n" .
-      "File: @libxmlerrorfile",
-      [
-        '@libxmlerrorcodename' => $error_code_name,
-        '@libxmlerrorcode' => $error->code,
-        '@libxmlerrormessage' => trim($error->message),
-        '@libxmlerrorline' => $error->line,
-        '@libxmlerrorcolumn' => $error->column,
-        '@libxmlerrorfile' => (($error->file)) ? $error->file : NULL,
-      ]
-    );
-  }
-
 }
diff --git a/src/Plugin/migrate_plus/data_parser/XmlTrait.php b/src/Plugin/migrate_plus/data_parser/XmlTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..25af9b6d46a0fb33564180fe052997081ab0bfc3
--- /dev/null
+++ b/src/Plugin/migrate_plus/data_parser/XmlTrait.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\migrate_plus\Plugin\migrate_plus\data_parser;
+
+/**
+ * Common functionality for XML data parsers.
+ */
+trait XmlTrait {
+
+  /**
+   * Registers the iterator's namespaces to a SimpleXMLElement.
+   *
+   * @param \SimpleXMLElement $xml
+   *   The element to apply namespace registrations to.
+   */
+  protected function registerNamespaces(\SimpleXMLElement $xml) {
+    if (is_array($this->configuration['namespaces'])) {
+      foreach ($this->configuration['namespaces'] as $prefix => $ns) {
+        $xml->registerXPathNamespace($prefix, $ns);
+      }
+    }
+  }
+
+  /**
+   * Parses a LibXMLError to a error message string.
+   *
+   * @param \LibXMLError $error
+   *   Error thrown by the XML.
+   *
+   * @return string
+   *   Error message
+   */
+  public static function parseLibXmlError(\LibXMLError $error) {
+    $error_code_name = 'Unknown Error';
+    switch ($error->level) {
+      case LIBXML_ERR_WARNING:
+        $error_code_name = t('Warning');
+        break;
+
+      case LIBXML_ERR_ERROR:
+        $error_code_name = t('Error');
+        break;
+
+      case LIBXML_ERR_FATAL:
+        $error_code_name = t('Fatal Error');
+        break;
+    }
+
+    return t(
+      "@libxmlerrorcodename @libxmlerrorcode: @libxmlerrormessage\n" .
+      "Line: @libxmlerrorline\n" .
+      "Column: @libxmlerrorcolumn\n" .
+      "File: @libxmlerrorfile",
+      [
+        '@libxmlerrorcodename' => $error_code_name,
+        '@libxmlerrorcode' => $error->code,
+        '@libxmlerrormessage' => trim($error->message),
+        '@libxmlerrorline' => $error->line,
+        '@libxmlerrorcolumn' => $error->column,
+        '@libxmlerrorfile' => (($error->file)) ? $error->file : NULL,
+      ]
+    );
+  }
+
+}