diff --git a/migrate.info b/migrate.info
index 6648be9..460d2b9 100644
--- a/migrate.info
+++ b/migrate.info
@@ -38,6 +38,7 @@ files[] = plugins/sources/sql.inc
 files[] = plugins/sources/sqlmap.inc
 files[] = plugins/sources/mssql.inc
 files[] = plugins/sources/oracle.inc
+files[] = plugins/sources/spreadsheet.inc
 files[] = plugins/sources/xml.inc
 files[] = tests/import/options.test
 files[] = tests/plugins/destinations/comment.test
diff --git a/plugins/sources/spreadsheet.inc b/plugins/sources/spreadsheet.inc
new file mode 100644
index 0000000..136a45b
--- /dev/null
+++ b/plugins/sources/spreadsheet.inc
@@ -0,0 +1,235 @@
+<?php
+
+/**
+ * @file
+ * Define a MigrateSource for importing from spreadsheet files.
+ *
+ * Requires the PHPExcel library to be installed.
+ * - Download PHPExcel at http://phpexcel.codeplex.com/.
+ * - Extract the archive to a temporary folder.
+ * - Ensure the hosting environment fulfills the requirements found in
+ *   install.txt.
+ * - Copy the contents of the Classes folder to an appropriate location
+ *   (sites/all/libraries/PHPExcel).
+ */
+
+/**
+ * Implements MigrateSource, to handle imports from XLS files.
+ */
+class MigrateSourceSpreadsheet extends MigrateSource {
+  /**
+   * PHPExcel object for storing the workbook data.
+   *
+   * @var PHPExcel
+   */
+  protected $workbook;
+
+  /**
+   * PHPExcel object for storing the worksheet data.
+   *
+   * @var PHPExcel_Worksheet
+   */
+  protected $worksheet;
+
+  /**
+   * The name of the worksheet that will be processed.
+   *
+   * @var string
+   */
+  protected $sheetName;
+
+  /**
+   * Number of rows in the worksheet that is being processed.
+   *
+   * @var integer
+   */
+  protected $rows = 0;
+
+  /**
+   * Number of columns in the worksheet that is being processed.
+   *
+   * @var integer
+   */
+  protected $cols = 0;
+
+  /**
+   * List of available source fields.
+   *
+   * @var array
+   */
+  protected $fields = array();
+
+  /**
+   * The current row number in the XLS file.
+   *
+   * @var integer+   */
+  protected $rowNumber;
+
+  /**
+   * The columns to be read from Excel
+   */
+  protected $columns;
+
+  /**
+   * Simple initialization.
+   *
+   * @param string $path
+   *   The path to the source file.
+   * @param string $sheet_name
+   *   The name of the sheet to be processed.
+   * @param array $options
+   *   Options applied to this source.
+   */
+  public function __construct($path, $sheet_name, $columns = array(), array $options = array()) {
+    parent::__construct($options);
+    $this->file = $path;
+    $this->sheetName = $sheet_name;
+    $this->columns = $columns;
+
+    // Load the workbook.
+    if ($this->load()) {
+      // Get the dimensions of the worksheet.
+      $this->rows = $this->worksheet->getHighestDataRow();
+      $this->cols = PHPExcel_Cell::columnIndexFromString($this->worksheet->getHighestDataColumn());
+
+      // Map field names to their column index.
+      for ($col = 0; $col < $this->cols; ++$col) {
+        $this->fields[$col] = trim($this->worksheet->getCellByColumnAndRow($col, 1)->getValue());
+      }
+
+      $this->unload();
+    }
+  }
+
+  /**
+   * Loads the workbook.
+   *
+   * @return bool
+   *   Returns true if the workbook was successfully loaded, otherwise false.
+   */
+  public function load() {
+    // Check that the file exists.
+    if (!file_exists($this->file)) {
+      Migration::displayMessage(t('The file !filename does not exist.', array('!filename' => $this->file)));
+      return FALSE;
+    }
+
+    if (module_exists('libraries') && module_exists('phpexcel')) {
+      $library = libraries_load('PHPExcel');
+      if (empty($library['loaded'])) {
+        Migration::displayMessage(t('The PHPExcel library could not be found.'));
+        return FALSE;
+      }
+    }
+    else {
+      Migration::displayMessage(t('The Libraries API module is not enabled.'));
+      return FALSE;
+    }
+
+    // Load the workbook.
+    try {
+      // Identify the type of the input file.
+      $type = PHPExcel_IOFactory::identify($this->file);
+      // Create a new Reader of the file type.
+      $reader = PHPExcel_IOFactory::createReader($type);
+      // Advise the Reader that we only want to load cell data.
+      $reader->setReadDataOnly(TRUE);
+      // Advise the Reader of which worksheet we want to load.
+      $reader->setLoadSheetsOnly($this->sheetName);
+      // Load the source file.
+      $this->workbook = $reader->load($this->file);
+      $this->worksheet = $this->workbook->getSheet();
+    }
+    catch (Exception $e) {
+      Migration::displayMessage(t('Error loading file: %message', array('%message' => $e->getMessage())));
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Unloads the workbook.
+   */
+  public function unload() {
+    $this->workbook->disconnectWorksheets();
+    unset($this->workbook);
+  }
+
+  /**
+   * Returns a string representing the source query.
+   *
+   * @return string
+   */
+  public function __toString() {
+    return $this->file;
+  }
+
+  /**
+   * Returns a list of fields available to be mapped from the source query.
+   *
+   * @return array
+   *   Keys: machine names of the fields (to be passed to addFieldMapping).
+   *   Values: Human-friendly descriptions of the fields.
+   */
+  public function fields() {
+    $fields = array();
+
+    foreach ($this->fields as $name) {
+      $fields[$name] = $name;
+    }
+
+    return $fields;
+  }
+
+  /**
+   * Returns a count of all available source records.
+   */
+  public function computeCount() {
+    // Subtract 1 for the header.
+    return $this->rows - 1;
+  }
+
+  /**
+   * Implements MigrateSource::performRewind().
+   *
+   * @return void
+   */
+  public function performRewind() {
+    // Initialize the workbook if it isn't already.
+    if (!isset($this->workbook)) {
+      $this->load();
+    }
+
+    $this->rowNumber = 1;
+  }
+
+  /**
+   * Implements MigrateSource::getNextRow().
+   *
+   * @return null|object
+   */
+  public function getNextRow() {
+    migrate_instrument_start('MigrateSourceSpreadsheet::next');
+    ++$this->rowNumber;
+
+    if ($this->rowNumber <= $this->rows) {
+      $row_values = array();
+      for ($col = 0; $col < $this->cols; ++$col) {
+        if (in_array($this->fields[$col], $this->columns) || empty($this->columns)) {
+          $row_values[$this->fields[$col]] = trim($this->worksheet->getCellByColumnAndRow($col, $this->rowNumber)->getValue());
+         }
+      }
+
+      return (object) $row_values;
+    }
+    else {
+      // EOF, close the workbook.
+      $this->unload();
+
+      migrate_instrument_stop('MigrateSourceSpreadsheet::next');
+      return NULL;
+    }
+  }
+}