getConfigFor($this); // State object holds information on the progress of parsing the given source. $state = $source->state(FEEDS_PARSE); $this->fetcher_result = $fetcher_result; $this->filepath = $fetcher_result->getFilePath(); _feeds_excel_include_libraries(); // Read file to object $this->reader = new Spreadsheet_Excel_Reader(); $this->reader->read($this->filepath); $start = $state->pointer ? $state->pointer : $this->pointer; $limit = $source->importer->getLimit(); $rows = $this->parseItems($start, $limit); // Report progress. $state->total = $this->sheet_count * $this->max_sheet_items; $state->pointer = $this->pointer; $progress = $this->completed_sheets * $this->max_sheet_items + count($rows); $state->progress($state->total, $progress); // Create a result object and return it. return new FeedsParserResult($rows, $source->feed_nid); } /** * Parse items from the given reader source respecting processing limits. * @param string $pointer * indicator for starting point for parsing items. Combination of sheet id * and item offset. e.g. '5:0' for sheet id 5 and no offset * */ public function parseItems($pointer, $limit) { $count = 0; list($last_sheet_id, $last_sheet_item) = explode(':', $pointer); $sheet_ids = $this->getSheetIDs(); $this->sheet_count = count($sheet_ids); // Indicates if we have to wait until we got to the last parsed sheet. $found_sheet = (!array_key_exists($last_sheet_id, $sheet_ids)); $parsed_items = array(); // Run through each sheet until we got enough items. foreach ($sheet_ids as $sheet_id) { $this->completed_sheets++; if ($found_sheet || $sheet_id == $last_sheet_id) { $found_sheet = TRUE; // Start off after the last element for known sheets. if ($sheet_id == $last_sheet_id) { $offset = $last_sheet_item + 1; } // Start off at 0 for new sheets. else { $offset = 0; } $sheet = $this->getSheet($sheet_id); // For the moment we parse the whole sheet at once. // @TODO: parse single rows to avoid unnecessary processing. $items = $this->getItems($sheet); $count_items = count($items); $this->max_sheet_items = max($this->max_sheet_items, $count_items); $left = $limit - $count; $sub = array_slice($items, $offset, $left, FALSE); $count_sub = count($sub); $parsed_items = array_merge($parsed_items, $sub); $count += $count_sub; $new_offset = $offset + count($sub); $this->pointer = $sheet_id .':'. ($new_offset - 1); // If we did not finish the sheet, do not count it. if ($count_items > $count_sub) { $this->completed_sheets--; } // If we got enough items, return them. if ($limit <= $count) { return $parsed_items; } // Else go on with running through the sheets. $last_sheet_id = $sheet_id; } } return $parsed_items; } /** * Get the defined sheet ids of sheets that shall get parsed. * @return array */ private function getSheetIDs() { $sheet_config = $this->config['sheets']; $available_sheets = array_keys($this->reader->sheets); // Determine all sheets we shall read in by definition. $sheet_ids = array(); // Either all sheets, if no special one is specified. if (strlen($sheet_config) < 1) { $sheet_ids = $available_sheets; } // Or sheets, specified by string expression. else { $sheet_expressions = explode(EXCELSHEET_SEP, $sheet_config); foreach($sheet_expressions as $expr) { list($from, $to) = explode(EXCELSHEET_RANGE, $expr); // Numeric Range if (is_numeric($from) && (!$to || is_numeric($to))) { if (!$to) { $to = $from; } // Add matching sheets to sheet ids foreach($available_sheets as $available_sheet) { if ($from <= $available_sheet && $available_sheet <= $to) { $sheet_ids[$available_sheet] = $available_sheet; } } } // Search for sheet name else { foreach ($this->reader->boundsheets as $sheet_id => $boundsheet) { if ($boundsheet['name'] == $expr) { $sheet_ids[$sheet_id] = $sheet_id; } } } } } return $sheet_ids; } /** * Retrieve all relevant sheets from dataset. */ private function getSheet($sheet_id) { if (!isset($this->sheets[$sheet_id])) { $excel_sheet = $this->reader->sheets[$sheet_id]; $excel_sheet['boundsheet_offset'] = $this->reader->boundsheets[$sheet_id]['offset']; $excel_sheet['name'] = $this->reader->boundsheets[$sheet_id]['name']; $excel_sheet['id'] = $sheet_id; //$sheets[$sheet_id] = token_get_values('excel_sheet', $sheet); $excel_sheet['fixed_cells'] = $this->getFixed($this->reader->sheets[$sheet_id]); $this->sheets[$sheet_id] = $excel_sheet; } return $this->sheets[$sheet_id]; } /** * Sort out the given fixed cells. */ private function getFixed($sheet) { // Store fixed cells in sheet $fixed = array(); if ($this->config['fixed']) { $max = array( 'rows' => $sheet['numRows'], 'cols' => $sheet['numCols'], ); $fixed_range = new ExcelRange($this->config['fixed'], $max); foreach ($sheet['cells'] as $row => $cols) { if ($fixed_range->isRowInAnyRange($row)) { foreach ($cols as $col => $val) { if ($fixed_range->isCellInAnyRange($row, $col)) { $cell = array( 'value' => $val, 'type' => $sheet['cellsInfo'][$row][$col]['type'], 'raw' => (isset($sheet['cellsInfo'][$row][$col]['raw'])) ? $sheet['cellsInfo'][$row][$col]['raw'] : $val, 'column' => $col, 'row' => $row, ); $fixed["$row-$col"] = $cell; } } } } } return $fixed; } /** * Retrieve Items from Spredsheet. * @param $sheet */ private function getItems($sheet) { $mode = $this->config['mapping_mode']; $items = array(); // Only run non-empty sheets if (!empty($sheet['cells'])) { $max = array( 'rows' => $sheet['numRows'], 'cols' => $sheet['numCols'], ); $iterative_range = new ExcelRange($this->config['iterative'], $max); $sheet_id = $sheet['id']; // Retrieve all items out of defined iterative Range. while ($range = $iterative_range->getNextRange()) { $range_id = $iterative_range->getCurrentRangeID(); $cells = $sheet['cells']; foreach ($cells as $row => $cols) { if ($iterative_range->isRowInCurrentRange($row)) { $row_offset = $iterative_range->getCurrentRangeRowOffset(); $max_rows = $iterative_range->getCurrentRangeMaxRow(); foreach($cols as $col => $value) { if ($iterative_range->isColInCurrentRange($col)) { $col_offset = $iterative_range->getCurrentRangeColOffset(); $max_cols = $iterative_range->getCurrentRangeMaxRow(); // Encode value and raw to utf8 in any case $value = utf8_encode($value); if (!isset($sheet['cellsInfo'][$row][$col]['raw'])) { $raw = $value; } else { $raw = utf8_encode($sheet['cellsInfo'][$row][$col]['raw']); } // Build cell $cell = array( 'value' => $value, 'type' => $sheet['cellsInfo'][$row][$col]['type'], 'raw' => $raw, 'column' => $col, 'row' => $row, 'x' => $col - $col_offset, 'y' => $row - $row_offset, ); // Set items dependent on switch ($mode) { case 'rows': $key = "$sheet_id:$range_id:$row"; $items[$key]['cells'][$col - $col_offset] = $cell; $items[$key]['row'] = $row; break; case 'columns': $key = "$sheet_id:$range_id:$col"; $items[$key]['cells'][$row - $row_offset] = $cell; $items[$key]['column'] = $col; break; } // Meta data for the given item if ($key && !isset($items[$key]['meta'])) { $items[$key]['meta'] = array( 'range_row_offset' => $row_offset, 'range_col_offset' => $col_offset, 'range_id' => $range_id, 'sheet_id' => $sheet_id, 'range_max_cols' => $max_cols, 'range_max_rows' => $max_rows, 'key' => $key, ); } } } } } } } return $items; } /** * Override parent::getMappingSources(). */ public function getMappingSources() { return FALSE; } /** * Return the token replaced value for the given element. */ public function getSourceElement(FeedsSource $source, FeedsParserResult $result, $element_key){ // excel file $types['excel-file'] = (object) array( 'filepath' => $this->filepath, ); // excel sheet $item = (object) $result->currentItem(); $data['excel-sheet'] = (object) $this->getSheet($item->meta['sheet_id']); // excel row or column switch ($this->config['mapping_mode']) { case 'rows': $data['excel-row'] = $item; break; case 'columns': $data['excel-column'] = $item; break; } // global $data['site'] = ''; // parent node if ($source->feed_nid) { $node = node_load($source->feed_nid); $data['node'] = $node; } $value = token_replace($element_key, $data, array('clear' => FALSE)); // For debug clear is set to FALSE; return $value; } /** * Define default configuration. */ public function configDefaults() { return array( 'mapping_mode' => 'rows', 'sheets' => '', 'header' => '', 'fixed' => '', 'iterative' => '1:10000', ); } /** * Build configuration form. */ public function configForm(&$form_state) { $form = array(); $form['mapping_mode'] = array( '#type' => 'select', '#title' => t('Mapping mode'), '#description' => t('Whether to map rows or columns.'), '#options' => array( 'rows' => t('Rows'), 'columns' => t('Columns'), ), '#default_value' => $this->config['mapping_mode'], ); $form['sheets'] = array( '#type' => 'textfield', '#title' => t('Sheets'), '#default_value' => $this->config['sheets'], '#description' => t('Enter the sheet names or IDs that shall be imported. Leaving empty means that all sheets are processed. Use , to separate different sheets. Use : to mark a range of IDs (e.g. 0:15).'), ); // Format string for range format. $format_single_1 = ''. EXCELRANGE_ROW .'1'. EXCELRANGE_COL .'2'; $format_single_2 = 'B1'; $format_range_1 = ''. EXCELRANGE_ROW .'1'. EXCELRANGE_COL .'2:'. EXCELRANGE_ROW .'5'. EXCELRANGE_COL .'4'; $format_range_2 = 'B1:D5'; $format_columns = 'B:D'; $format_rows = '1:5'; $format = t('You can use different range formats: !formats
Use !sep to separate multiple range entries.', array('!sep' => ''. EXCELRANGE_SEP .'', '!formats' => "$format_single_1, $format_single_2, $format_range_1, $format_range_2, $format_columns, $format_rows")); $form['fixed'] = array( '#type' => 'textfield', '#title' => t('Fixed Range'), '#description' => t('Range of the sheet, that can be referenced from any item.
!format', array('!format' => $format)), '#default_value' => $this->config['fixed'], ); $form['iterative'] = array( '#type' => 'textfield', '#title' => t('Iterative range'), '#description' => t('Enter a range that feeds should run through for iterative mapping to process items.
!format', array('!format' => $format)), '#required' => TRUE, '#default_value' => $this->config['iterative'], ); $form['header'] = array( '#type' => 'textfield', '#title' => t('Header range'), '#description' => t('Enter the range that can be used as row or column aliases for a given iterative range.
!format', array('!format' => $format)), '#default_value' => $this->config['header'], '#disabled' => TRUE, '#prefix' => 'Currently disabled option:', ); return $form; } /** * Validate rows and columns input in ConfigForm. */ public function configFormValidate (&$values) { // Check valid range formats. $range_fields = array('iterative', 'fixed', 'header'); foreach ($range_fields as $field) { if ($values[$field]) { $range = new ExcelRange($values[$field]); if (!$range->converted_all_ranges()) { form_set_error($field, t('The range expression could not be completely converted in a valid range format.')); } } } } }