You are here

recipe_recipeML.module in Recipe 7

recipe_recipeML.module - Enables importing and exporting of recipeML format recipes.

File

includes/recipe_recipeML.module
View source
<?php

/**
 * @file
 * recipe_recipeML.module - Enables importing and exporting of recipeML format recipes.
 */

/**
 * Implementation of hook_recipeio($type).
 */
function recipe_recipeML_recipeio($type) {
  $supported = array(
    'export_single' => array(
      'format_name' => t('recipeML'),
      'callback' => 'recipe_recipeML_export_single',
      'format_help' => t('Export to a recipeML based xml format.'),
    ),
    'export_multi' => array(
      'format_name' => t('recipeML'),
      'callback' => 'recipe_recipeML_export_multi',
      'format_help' => t('Export all recipes to recipeML based xml format.'),
    ),
    'import_multi' => array(
      'format_name' => t('recipeML'),
      'callback' => 'recipe_recipeML_import_multi',
      'format_help' => t('Import recipes from a recipeML based xml file.'),
    ),
  );
  if (isset($supported[$type])) {
    return array(
      'recipeml' => $supported[$type],
    );
  }
  else {
    return FALSE;
  }
}
function recipe_recipeML_export_multi() {
  $o = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<!DOCTYPE recipeml PUBLIC "-//FormatData//DTD RecipeML 0.5//EN" "http://www.formatdata.com/recipeml/recipeml.dtd">' . "\n" . '<recipeml version="0.5" generator="http://drupal.org/project/recipe">' . "\n";

  // you should not be able to export unpublished recipes
  $result = db_query("SELECT n.nid from {node} n WHERE n.type='recipe' and n.status>0 ORDER BY n.title");
  foreach ($result as $record) {
    $o .= recipe_recipeML_export_single($record->nid, NULL, FALSE);
  }
  $o .= '</recipeml>';
  drupal_add_http_header('Content-type', 'text/xml; charset=utf-8');
  print $o;
}
function recipe_recipeML_export_single($nid = NULL, $yield = NULL, $add_content_header = TRUE) {
  if ($nid === NULL) {
    drupal_set_message(t('Recipe not found.'));
    drupal_not_found();
    return;
  }
  $node = node_load($nid);

  // you should not be able to export unpublished recipes
  if ($node->status == 0) {
    drupal_access_denied();
    return;
  }

  // Set the custom yield so we can scale up/down the recipe quantities.
  $node->recipe_custom_yield = $yield;
  $factor = 1;
  if (isset($node->recipe_custom_yield)) {
    $factor = $node->recipe_custom_yield / $node->recipe_yield;
    $node->recipe_yield = $node->recipe_custom_yield;
  }
  $cat_string = '';

  /*
    $vocabs = taxonomy_get_vocabularies('recipe');

    foreach ($vocabs as $vocab) {
      $terms = taxonomy_node_get_terms_by_vocabulary($node, $vocab->vid);
      foreach ( $terms as $term ) {
        $term = array_shift($terms);
        $cat_string .= $term->name . ', ';
      }
      $cat_string = substr($cat_string, 0, -2);
    }
    if ( $cat_string != '' ) {
      $cat_string = '<categories>'. $cat_string .'</categories>';
    }
  */
  $output = '';
  if ($add_content_header == TRUE) {
    $output .= '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . '<!DOCTYPE recipeml PUBLIC "-//FormatData//DTD RecipeML 0.5//EN" "http://www.formatdata.com/recipeml/recipeml.dtd">' . "\n" . '<recipeml version="0.5" generator="http://drupal.org/project/recipe">' . "\n";
  }
  $output .= '<recipe>' . "\n" . '<head>' . "\n" . '<title>' . my_xml_escape($node->title) . "</title>\n" . '      <version>' . date('m-d-Y', $node->changed) . '</version>' . "\n" . '      <source>' . my_xml_escape($node->recipe_source == '' ? url('node/' . $node->nid, array(
    'absolute' => TRUE,
  )) : $node->recipe_source) . '</source>' . "\n" . '      <yield><qty>' . floatval($node->recipe_yield) . '</qty><unit>' . my_xml_escape($node->recipe_yield_unit == '' ? t('Servings') : $node->recipe_yield_unit) . '</unit></yield>' . "\n" . '      <preptime type="cooking"><time><qty>' . $node->recipe_preptime . '</qty><timeunit>minutes</timeunit></time></preptime>' . "\n{$cat_string}" . '</head>' . "\n" . '<description>' . my_xml_escape($node->recipe_description) . '</description>' . "\n" . '<ingredients>';
  $unit_list = recipe_get_units();
  foreach ($node->recipe_ingredients['ing'] as $ingredient) {
    $prep = '';
    if (strlen($ingredient['note']) > 0) {
      $prep = '<prep>' . my_xml_escape($ingredient['note']) . '</prep>';
    }
    if ($ingredient['quantity'] > 0) {
      $ingredient['quantity'] *= $factor;
    }
    if (isset($unit_list[$ingredient['unit_key']])) {

      // Print the singular or plural term depending on the quantity.
      $title = $ingredient['quantity'] > 1 ? $unit_list[$ingredient['unit_key']]['plural'] : $unit_list[$ingredient['unit_key']]['name'];
    }
    else {
      $title = $ingredient['unit_key'];
    }

    // Print the abbreviation if recipe_unit_display says to or the abbreviation is blank (ie = Unit, which we don't print).
    if (!isset($ingredient['abbreviation']) && isset($unit_list[$ingredient['unit_key']])) {
      $ingredient['abbreviation'] = $unit_list[$ingredient['unit_key']]['abbreviation'];
    }
    if (empty($ingredient['abbreviation'])) {
      $ingredient['abbreviation'] = ' ';
    }
    $ingredient['str_unit'] = '';
    if (variable_get('recipe_unit_display', 0) == 0 || $ingredient['abbreviation'] == ' ') {
      $ingredient['str_unit'] = $ingredient['abbreviation'];
    }
    else {
      $ingredient['str_unit'] = $title;
    }
    $output .= "\n" . '<ing><amt><qty>' . $ingredient['quantity'] . '</qty><unit>' . $ingredient['str_unit'] . '</unit></amt><item>' . my_xml_escape($ingredient['name']) . "</item>{$prep}</ing>";
  }
  $output .= "\n" . '</ingredients>' . "\n" . '<directions>' . my_xml_escape($node->recipe_instructions) . '</directions>' . "\n" . '<note>' . my_xml_escape($node->recipe_notes) . '</note>' . "\n" . '</recipe>' . "\n";
  if ($add_content_header == TRUE) {
    $output .= '</recipeml>';
    drupal_add_http_header('Content-type', 'text/xml; charset=utf-8');
  }
  return $output;
}
function my_xml_escape($string) {
  $chars = array(
    '&' => '&amp;',
    '<' => '&lt;',
    '>' => '&gt;',
    '"' => '&quot;',
    '\'' => '&apos;',
  );
  return str_replace(array_keys($chars), array_values($chars), $string);
}
function recipe_recipeML_import_multi() {
  $o = drupal_get_form('recipe_recipeML_import_form');
  return $o;
}
function recipe_recipeML_import_form($form_state) {
  $form = array();
  $form['#attributes'] = array(
    'enctype' => "multipart/form-data",
  );
  $form['recipe_import_file'] = array(
    '#type' => 'file',
    '#title' => t('RecipeML File'),
    '#default_value' => '',
    '#size' => 34,
    '#description' => t("A Recipe in RecipeML format, see http://www.formatdata.com/recipeml.  Note: This will add taxonomy terms to the lightest weight recipe taxonomy."),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );
  return $form;
}
function recipe_recipeML_import_form_submit($form, &$form_state) {
  global $recipes, $recipe, $data_string;
  $data = '';
  $validators = array(
    'file_validate_extensions' => array(
      'mxp xml',
    ),
  );
  if ($file = file_save_upload('recipe_import_file', $validators, FALSE, FILE_EXISTS_RENAME)) {

    // Load the xml string.
    $data = file_get_contents($file->uri);
    drupal_set_message(t('The attached file was successfully uploaded'));
  }
  else {
    drupal_set_message(t('The attched file failed to upload.'), 'error');
    return;
  }

  // Parse the data.
  $xml_parser = drupal_xml_parser_create($data);
  xml_set_element_handler($xml_parser, 'recipe_import_element_start', 'recipe_import_element_end');
  xml_set_character_data_handler($xml_parser, 'recipe_import_element_data');
  if (!xml_parse($xml_parser, $data, 1)) {
    watchdog('recipe', 'Failed to parse RecipeML file: @error at line @line.', array(
      '@error' => xml_error_string(xml_get_error_code($xml_parser)),
      '@line' => xml_get_current_line_number($xml_parser),
    ), WATCHDOG_WARNING);
  }

  // Free the parser.
  xml_parser_free($xml_parser);
  if ($_POST['op'] == t('Import')) {

    //    $vocabs = taxonomy_get_vocabularies('recipe');
    //    list($lightest_vid, $vocab) = each($vocabs);
    //    reset($vocabs);
    foreach ($recipes as $import_recipe_array) {

      //      $recipe->taxonomy = array();

      /*
            foreach ($recipe->_categories as $category) {

              // Search the lightest weight recipe vocab for this term.
              $term = recipe_get_term_by_name($category, $lightest_vid);

              // You didn't find that term, so add it.
              if ( $term == FALSE && isset($lightest_vid) ) {
                $term = array('name' => $category, 'vid' => $lightest_vid);
                drupal_set_message(t('Adding term %term_name', array('%term_name' => $category)));
                taxonomy_save_term($term);
                // Cast back to object so it's like the return value from recipe_get_term_by_name().
                $term = (object)$term;
              }

              // You have the term now (existing or new), link it ink.
              if ( isset($term) ) {
                $recipe->taxonomy[] = $term->tid;
              }

            }
      */
      if (($node = recipe_import_get_node($import_recipe_array)) != FALSE) {
        node_save($node);
      }
    }
  }
}

/**
 * Call-back function used by the XML parser.
 */
function recipe_import_element_start($parser, $name, $attributes) {
  global $recipe;
  switch ($name) {
    case 'RECIPE':
      $recipe = array(
        'title' => '',
        'description' => '',
        'yield' => '1',
        'yield_unit' => 'Servings',
        'cooktime' => 0,
        'categories' => array(),
        'ingredients' => array(),
        'instructions' => '',
        'notes' => '',
        'source' => '',
        '__region' => '',
      );
      break;
    case 'INGREDIENTS':
      $recipe['__region'] = 'ingredients';
      break;
    case 'ING':
      $ingredient = array(
        'ingredient_name' => '',
        'ingredient_note' => '',
        'quantity' => '',
        'unit_name' => '',
        'unit_key' => FALSE,
        'ingredient_note' => '',
      );
      $recipe['ingredients'][] = $ingredient;
      $recipe['__region'] = 'ING';
      break;
    case 'YIELD':
      $recipe['__region'] = 'YIELD';
      break;
    case 'PREPTIME':
      $recipe['__region'] = 'PREPTIME';
      break;
  }
}

/**
 * Call-back function used by the XML parser.
 */
function recipe_import_element_end($parser, $name) {
  global $recipes, $recipe, $data_string;
  switch ($name) {
    case 'RECIPE':
      if ($recipe['yield'] == 0) {
        $recipe['yield'] = 1;
      }
      if (empty($recipe['title'])) {
        $recipe['title'] = "RecipeML auto title";
      }
      if (empty($recipe['description'])) {
        $recipe['description'] = "RecipeML auto description.";
      }
      if (empty($recipe['notes'])) {
        $recipe['notes'] = "Imported from RecipeML file";
      }
      $recipes[] = $recipe;
      break;
    case 'YIELD':
      $recipe['__region'] = '';
      break;
    case "TITLE":
      $recipe['title'] = trim($data_string);
      break;
    case "SOURCE":
      $recipe['source'] = trim($data_string);
      break;
    case "CATEGORIES":
      $xmlterms = explode(',', $data_string);
      foreach ($xmlterms as $xmlterm) {
        $recipe['categories'][] = trim($xmlterm);
      }
      break;

    // QTY tag appears in more than one container tag.
    case 'QTY':
      if ($recipe['__region'] == 'YIELD') {
        $recipe['yield'] = trim($data_string);
      }
      elseif ($recipe['__region'] == 'PREPTIME') {
        $recipe['preptime'] = trim($data_string);
      }
      else {
        $ing = array_pop($recipe['ingredients']);
        $ing['quantity'] = trim($data_string);
        $recipe['ingredients'][] = $ing;
      }
      break;
    case 'UNIT':

      // UNIT is contained in PREPTIME, YIELD, as well as ING->AMT.
      if ($recipe['__region'] == 'ING') {
        $ing = array_pop($recipe['ingredients']);
        $ing['unit_name'] = trim($data_string);

        // Try to match unit.
        if ($unit_key = recipe_unit_fuzzymatch(trim($data_string))) {
          $ing['unit_key'] = $unit_key;
        }
        else {
          $ing['unit_key'] = 'unit';
          if (strlen($ing['ingredient_name']) > 0) {
            $ing['ingredient_name'] = $ing['ingredient_name'] . ' ' . trim($data_string);
          }
          else {
            $ing['ingredient_name'] = trim($data_string);
          }
        }
        $recipe['ingredients'][] = $ing;
      }
      break;
    case 'ITEM':
      $ing = array_pop($recipe['ingredients']);
      if (strlen($ing['ingredient_name']) > 0) {
        $ing['ingredient_name'] = trim($data_string) . ' ' . $ing['ingredient_name'];
      }
      else {
        $ing['ingredient_name'] = trim($data_string);
      }
      $recipe['ingredients'][] = $ing;
      break;
    case 'DIRECTIONS':
      $recipe['instructions'] .= trim($data_string);
      break;
    case 'STEP':
      $recipe['instructions'] .= trim($data_string) . "\n";
      break;
    case 'NOTE':
      $recipe['notes'] = trim($data_string);
      break;
    case 'SOURCE':
      $recipe['source'] = trim($data_string);
      break;
    case 'PREP':
      $ing = array_pop($recipe['ingredients']);
      $ing['ingredient_note'] = trim($data_string);
      $recipe['ingredients'][] = $ing;
    case 'PREPTIME':
      $recipe['__region'] = '';
      break;
  }
  $data_string = '';
}

/**
 * Call-back function used by the XML parser.
 */
function recipe_import_element_data($parser, $data) {
  global $data_string;
  $data_string .= $data;
}
function recipe_get_term_by_name($name, $vocab_id) {
  $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE t.vid=%d and LOWER(t.name) = LOWER('%s')", 't', 'tid'), $vocab_id, trim($name));
  $result = array();
  while ($term = db_fetch_object($db_result)) {
    return $term;
  }
  return FALSE;
}

Functions

Namesort descending Description
my_xml_escape
recipe_get_term_by_name
recipe_import_element_data Call-back function used by the XML parser.
recipe_import_element_end Call-back function used by the XML parser.
recipe_import_element_start Call-back function used by the XML parser.
recipe_recipeML_export_multi
recipe_recipeML_export_single
recipe_recipeML_import_form
recipe_recipeML_import_form_submit
recipe_recipeML_import_multi
recipe_recipeML_recipeio Implementation of hook_recipeio($type).