Как лучше всего использовать регулярные выражения для преобразования Heirarchical Text File в XML? - PullRequest
3 голосов
/ 18 февраля 2010

Доброе утро -

Мне интересно посмотреть на эффективный способ анализа значений текстового файла heirarchical (т. Е. Файла с заголовком => Несколько заголовков => Несколько подзаголовков => Несколько ключей => Несколько Значения) в простой документ XML. Для простоты ответ будет написан с использованием:

  • Regex (желательно на PHP)
  • или код PHP (например, если циклы были более эффективными)

Вот пример файла инвентаря, с которым я работаю. Обратите внимание, что заголовок = FOODS , подзаголовок = тип (A, B ...) , ключи = PRODUCT (или CODE и т. Д.) и значения может иметь еще одну строчку.

**FOODS - TYPE A**
___________________________________
**PRODUCT**
1) Mi Pueblito Queso Fresco Authentic Mexican Style Fresh Cheese;
2) La Fe String Cheese
**CODE**
Sell by date going back to February 1, 2009
**MANUFACTURER**
Quesos Mi Pueblito, LLC, Passaic, NJ.
**VOLUME OF UNITS**
11,000 boxes
**DISTRIBUTION**
NJ, NY, DE, MD, CT, VA
___________________________________
**PRODUCT**
1) Peanut Brittle No Sugar Added;
2) Peanut Brittle Small Grind;
3) Homestyle Peanut Brittle Nuggets/Coconut Oil Coating
**CODE**
1) Lots 7109 - 8350 inclusive;
2) Lots 8198 - 8330 inclusive;
3) Lots 7075 - 9012 inclusive;
4) Lots 7100 - 8057 inclusive;
5) Lots 7152 - 8364 inclusive
**MANUFACTURER**
Star Kay White, Inc., Congers, NY.
**VOLUME OF UNITS**
5,749 units
**DISTRIBUTION**
NY, NJ, MA, PA, OH, FL, TX, UT, CA, IA, NV, MO and IN
**FOODS - TYPE B**
___________________________________
**PRODUCT**
Cool River Bebidas Naturales - West Indian Cherry Fruit Acerola 16% Juice;
**CODE**
990-10/2 10/5
**MANUFACTURER**
San Mar Manufacturing Corp., Catano, PR.
**VOLUME OF UNITS**
384
**DISTRIBUTION**
PR

И вот желаемый вывод (прошу прощения за любые синтаксические ошибки XML):

<foods>
    <food type = "A" >
        <product>Mi Pueblito Queso Fresco Authentic Mexican Style Fresh Cheese</product>
        <product>La Fe String Cheese</product>
        <code>Sell by date going back to February 1, 2009</code>
        <manufacturer>Quesos Mi Pueblito, LLC, Passaic, NJ.</manufacturer>
        <volume>11,000 boxes</volume>
        <distibution>NJ, NY, DE, MD, CT, VA</distribution>
    </food>
    <food type = "A" >
        <product>Peanut Brittle No Sugar Added</product>
        <product>Peanut Brittle Small Grind</product>
        <product>Homestyle Peanut Brittle Nuggets/Coconut Oil Coating</product>
        <code>Lots 7109 - 8350 inclusive</code>
    <code>Lots 8198 - 8330 inclusive</code>
    <code>Lots 7075 - 9012 inclusive</code>
    <code>Lots 7100 - 8057 inclusive</code>
    <code>Lots 7152 - 8364 inclusive</code>
        <manufacturer>Star Kay White, Inc., Congers, NY.</manufacturer>
        <volume>5,749 units</volume>
        <distibution>NY, NJ, MA, PA, OH, FL, TX, UT, CA, IA, NV, MO and IN</distribution>
    </food>
    <food type = "B" >
        <product>Cool River Bebidas Naturales - West Indian Cherry Fruit Acerola 16% Juice</product>
        <code>990-10/2 10/5</code>
        <manufacturer>San Mar Manufacturing Corp., Catano, PR</manufacturer>
        <volume>384</volume>
        <distibution>PR</distribution>
    </food>
</FOODS>
<!-- and so forth -->

Пока что мой подход (который может быть довольно неэффективным при использовании огромного текстового файла) будет одним из следующих:

  1. Циклы и несколько операторов Select / Case , где файл загружается в строковый буфер, и, просматривая каждую строку, проверяет, соответствует ли он одной из строк заголовка / подзаголовка / ключа , добавьте соответствующий тег xml к строковой переменной xml, а затем добавьте дочерние узлы в xml на основе инструкций IF относительно того, какое имя ключа является самым последним (что кажется трудоемким и подверженным ошибкам, особенно если текст меняется даже немного) - ИЛИ

  2. Используйте REGEX (регулярные выражения) , чтобы найти и заменить ключевые поля соответствующими тегами XML, очистить их с помощью библиотеки XML и экспортировать файл XML. Проблема в том, что я почти не использую регулярные выражения, поэтому мне нужна помощь на основе примеров .

Буду признателен за любую помощь или совет.

Спасибо.

Ответы [ 4 ]

2 голосов
/ 19 февраля 2010

Пример, который вы можете использовать в качестве отправной точки. По крайней мере, я надеюсь, что это дает вам представление ...

<?php
define('TYPE_HEADER', 1);
define('TYPE_KEY', 2);
define('TYPE_DELIMETER', 3);
define('TYPE_VALUE', 4);

$datafile = 'data.txt';
$fp = fopen($datafile, 'rb') or die('!fopen');

// stores (the first) {header} in 'name' and the root simplexmlelement in 'element'
$container = array('name'=>null, 'element'=>null);
// stores the name for each item element, the value for the type attribute for subsequent item elements and the simplexmlelement of the current item element
$item = array('name'=>null, 'type'=>null, 'current_element'=>null);
// the last **key** encountered, used to create new child elements in the current item element when a value is encountered
$key = null;

while ( false!==($t=getstruct($fp)) ) {
  switch( $t[0] ) {
    case TYPE_HEADER:
      if ( is_null($container['element']) ) {
        // this is the first time we hit **header - subheader**
        $container['name'] = $t[1][0];
        // ugly hack, < . name . />
        $container['element'] = new SimpleXMLElement('<'.$container['name'].'/>');
        // each subsequent new item gets the new subheader as type attribute
        $item['type'] = $t[1][1];
        // dummy implementation: "deducting" the item names from header/container[name]
        $item['name'] = substr($t[1][0], 0, -1);
      }
      else {
        // hitting **header - subheader** the (second, third, nth) time 
        /*
        header must be the same as the first time (stored in container['name']).
        Otherwise you need another container element since 
        xml documents can only have one root element
        */
        if ( $container['name'] !== $t[1][0] ) {
          echo $container['name'], "!==",  $t[1][0], "\n";
          die('format error');
        }
        else {
          // subheader may have changed, store it for future item elements
          $item['type'] = $t[1][1];
        }
      }
      break;
    case TYPE_DELIMETER:
      assert( !is_null($container['element']) );
      assert( !is_null($item['name']) );
      assert( !is_null($item['type']) );
      /* that's maybe not a wise choice.
      You might want to check the complete item before appending it to the document.
      But the example is a hack anyway ...so create a new item element and append it to the container right away
      */
      $item['current_element'] = $container['element']->addChild($item['name']);
      // set the type-attribute according to the last **header - subheader** encountered
      $item['current_element']['type'] = $item['type'];
      break;
    case TYPE_KEY:
      $key = $t[1][0];
      break;
    case TYPE_VALUE:
      assert( !is_null($item['current_element']) );
      assert( !is_null($key) );
      // this is a value belonging to the "last" key encountered
      // create a new "key" element with the value as content
      // and addit to the current item element
      $tmp = $item['current_element']->addChild($key, $t[1][0]);
      break;
    default:
      die('unknown token');
  }
}

if ( !is_null($container['element']) ) {
  $doc = dom_import_simplexml($container['element']);
  $doc = $doc->ownerDocument;
  $doc->formatOutput = true;
  echo $doc->saveXML();
}
die;


/*
Take a look at gettoken() at http://www.tuxradar.com/practicalphp/21/5/6
It breaks the stream into much simpler pieces.
In the next step the parser would "combine" or structure the simple tokens into more complex things.
This function does both....
@return array(id, array(parameter)
*/
function getstruct($fp) {
  if ( feof($fp) ) {
    return false;
  }
  // shortcut: all we care about "happens" on one line
  // so let php read one line in a single step and then do the pattern matching
  $line = trim(fgets($fp));

  // this matches **key** and **header - subheader**
  if ( preg_match('#^\*\*([^-]+)(?:-(.*))?\*\*$#', $line, $m) ) {
    // only for **header - subheader** $m[2] is set.
    if ( isset($m[2]) ) {
      return array(TYPE_HEADER, array(trim($m[1]), trim($m[2])));
    }
    else {
      return array(TYPE_KEY, array($m[1]));
    }
  }
  // this matches _____________ and means "new item"
  else if ( preg_match('#^_+$#', $line, $m) ) {
    return array(TYPE_DELIMETER, array());
  }
  // any other non-empty line is a single value
  else if ( preg_match('#\S#', $line) ) {
    // you might want to filter the 1),2),3) part out here
    // could also be two diffrent token types
    return array(TYPE_VALUE, array($line));
  }
  else {
    // skip empty lines, would be nicer with tail-recursion...
    return getstruct($fp);
  }
}

печать

<?xml version="1.0"?>
<FOODS>
  <FOOD type="TYPE A">
    <PRODUCT>1) Mi Pueblito Queso Fresco Authentic Mexican Style Fresh Cheese;</PRODUCT>
    <PRODUCT>2) La Fe String Cheese</PRODUCT>
    <CODE>Sell by date going back to February 1, 2009</CODE>
    <MANUFACTURER>Quesos Mi Pueblito, LLC, Passaic, NJ.</MANUFACTURER>
    <VOLUME OF UNITS>11,000 boxes</VOLUME OF UNITS>
    <DISTRIBUTION>NJ, NY, DE, MD, CT, VA</DISTRIBUTION>
  </FOOD>
  <FOOD type="TYPE A">
    <PRODUCT>1) Peanut Brittle No Sugar Added;</PRODUCT>
    <PRODUCT>2) Peanut Brittle Small Grind;</PRODUCT>
    <PRODUCT>3) Homestyle Peanut Brittle Nuggets/Coconut Oil Coating</PRODUCT>
    <CODE>1) Lots 7109 - 8350 inclusive;</CODE>
    <CODE>2) Lots 8198 - 8330 inclusive;</CODE>
    <CODE>3) Lots 7075 - 9012 inclusive;</CODE>
    <CODE>4) Lots 7100 - 8057 inclusive;</CODE>
    <CODE>5) Lots 7152 - 8364 inclusive</CODE>
    <MANUFACTURER>Star Kay White, Inc., Congers, NY.</MANUFACTURER>
    <VOLUME OF UNITS>5,749 units</VOLUME OF UNITS>
    <DISTRIBUTION>NY, NJ, MA, PA, OH, FL, TX, UT, CA, IA, NV, MO and IN</DISTRIBUTION>
  </FOOD>
  <FOOD type="TYPE B">
    <PRODUCT>Cool River Bebidas Naturales - West Indian Cherry Fruit Acerola 16% Juice;</PRODUCT>
    <CODE>990-10/2 10/5</CODE>
    <MANUFACTURER>San Mar Manufacturing Corp., Catano, PR.</MANUFACTURER>
    <VOLUME OF UNITS>384</VOLUME OF UNITS>
    <DISTRIBUTION>PR</DISTRIBUTION>
  </FOOD>
</FOODS>

К сожалению, состояние модуля php для ANTLR в настоящее время равно " Время выполнения находится в альфа-состоянии. ", но в любом случае стоит попробовать ...

1 голос
/ 18 февраля 2010

См .: http://www.tuxradar.com/practicalphp/21/5/6

Здесь рассказывается, как анализировать текстовый файл в токены с помощью PHP. После разбора вы можете поместить его во что угодно.

Вам необходимо выполнить поиск определенных токенов в файле на основе ваших критериев:

например: ПРОДУКТ

Это дает вам тег XML

Тогда 1) может иметь особое значение

1) Арахис хрупкий ...

Здесь указывается, что помещать в тег XML.

Я не знаю, является ли это наиболее эффективным способом выполнения вашей задачи, но именно так компилятор будет анализировать файл и может сделать его очень точным.

0 голосов
/ 19 февраля 2010

Еще один совет для решения XSLT 1.0 здесь: http://bytes.com/topic/net/answers/808619-read-plain-file-xslt-1-0-a

0 голосов
/ 19 февраля 2010

Вместо Regex или PHP используйте функцию unsarsed-text () XSLT 2.0 для чтения файла (см. http://www.biglist.com/lists/xsl-list/archives/200508/msg00085.html)

...