Лучший способ разобрать динамический текстовый список в PHP

Ниже у меня есть список текстов, это из популярной онлайн-игры под названием EVE Online, и в основном это приходит вам по почте, когда вы убиваете человека в игре. Я создаю инструмент для их анализа с помощью PHP для извлечения всей необходимой информации. Мне понадобятся все показанные фрагменты информации, и я пишу классы, чтобы красиво разбить их на соответствующие инкапсулированные данные.

2008.06.19 20:53:00

Victim: Massi
Corp: Cygnus Alpha Syndicate
Alliance: NONE
Faction: NONE
Destroyed: Raven
System: Jan
Security: 0.4
Damage Taken: 48436

Involved parties:

Name: Kale Kold
Security: -10.0
Corp: Vicious Little Killers
Alliance: NONE
Faction: NONE
Ship: Drake
Weapon: Hobgoblin II
Damage Done: 22093

Name: Harulth (laid the final blow)
Security: -10.0
Corp: Vicious Little Killers
Alliance: NONE
Faction: NONE
Ship: Drake
Weapon: Caldari Navy Scourge Heavy Missile
Damage Done: 16687

Name: Gistatis Tribuni / Angel Cartel
Damage Done: 9656

Destroyed items:

Capacitor Power Relay II, Qty: 2
Paradise Cruise Missile, Qty: 23
Cataclysm Cruise Missile, Qty: 12
Small Tractor Beam I
Alloyed Tritanium Bar, Qty: 2 (Cargo)
Paradise Cruise Missile, Qty: 1874 (Cargo)
Contaminated Nanite Compound (Cargo)
Capacitor Control Circuit I, Qty: 3
Ballistic Deflection Field I
'Malkuth' Cruise Launcher I, Qty: 3
Angel Electrum Tag, Qty: 2 (Cargo)

Dropped items:

Ballistic Control System I
Shield Boost Amplifier I, Qty: 2
Charred Micro Circuit, Qty: 4 (Cargo)
Capacitor Power Relay II, Qty: 2
Paradise Cruise Missile, Qty: 10
Cataclysm Cruise Missile, Qty: 21
X-Large Shield Booster II
Cataclysm Cruise Missile, Qty: 3220 (Cargo)
Fried Interface Circuit (Cargo)
F-S15 Braced Deflection Shield Matrix, Qty: 2
Salvager I
'Arbalest' Cruise Launcher I
'Malkuth' Cruise Launcher I, Qty: 2

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

Во-первых, раздел «Вовлеченные стороны:» является динамическим и может содержать множество людей с аналогичной структурой, как показано ниже, но если противник, управляемый компьютером, также стреляет в жертву, он сокращается до только «Имя» и «Урон». Поля «Готово», как показано выше (Gistatis Tribuni / Angel Cartel).

Во-вторых, элементы «Уничтожено» и «Выброшено» являются динамическими и будут иметь разную длину в каждом письме, и мне также нужно будет получить количество и независимо от того, находятся ли они в грузе.

Идеи для подхода приветствуются.


person Gary Willoughby    schedule 28.11.2008    source источник


Ответы (3)


Если вам нужно что-то гибкое, используйте подход конечного автомата.

Если вы хотите что-то быстрое и грязное, используйте регулярное выражение.

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

<?php

class Parser 
{
   /* Enclosing the parser in a class is not mandatory but it' clean */

    function Parser()
    {

        /* data holder */
        $this->date = '';
        $this->parties = array();
        $this->victim = array();
        $this->items = array("Destroyed" => array(),
                                            "Dropped" => array());

        /* Map you states on actions. Sub states can be necessary (and sub parsers too :-) */                   
        $this->states = array('Victim' => 'victim_parsing',
                                             'Involved' => 'parties_parsing' ,
                                             'items:' => "item_parsing");


        $this->state = 'start';                      
        $this->item_parsing_state = 'Destroyed';     
        $this->partie_parsing_state = '';           
        $this->parse_tools = array('start' => 'start_parsing',
                                           'parties_parsing' =>'parties_parsing',
                                           'item_parsing' => 'item_parsing',
                                           'victim_parsing' => 'victim_parsing');


    }

    /* the magic job is done here */

    function checkLine($line) 
    {
        foreach ($this->states as $keyword => $state) 
            if (strpos($line, $keyword) !== False)
                    $this->state = $this->states[$keyword];

        return trim($line);
    }

    function parse($file)
    {
        $this->file = new SplFileObject($file);
        foreach ($this->file as $line) 
            if ($line = $this->checkLine($line))
                 $this->{$this->parse_tools[$this->state]}($line);
    }


    /* then here you can define as much as parsing rules as you want */

    function victim_parsing($line) 
    {
        $victim_caract = explode(': ', $line);
        $this->victim[$victim_caract[0]] = $victim_caract[1];
    }

    function start_parsing($line)
    {
        $this->date = $line;
    }

    function item_parsing($line) 
    {
        if (strpos($line, 'items:') !== False)
        {
            $item_state = explode(' ', $line);
            $this->item_parsing_state = $item_state[0];
        }   
          else 
         {
               $item_caract = explode(', Qty: ', $line);
               $this->items[$this->item_parsing_state][$item_caract[0]] = array();
               $item_infos =  explode(' ', $item_caract[1]);
               $this->items[$this->item_parsing_state][$item_caract[0]] ['qty'] = empty($item_infos[0]) ? 1 : $item_infos[0];
               $this->items[$this->item_parsing_state][$item_caract[0]] ['cargo'] = !empty( $item_infos[1]) ? "True":  "False";
               if  (empty( $this->items[$this->item_parsing_state][$item_caract[0]] ['qty'] ))
                print $line;
         }
    }

    function parties_parsing($line) 
    {        

        $partie_caract = explode(': ', $line);

        if ($partie_caract[0] == "Name")
        {
            $this->partie_parsing_state = $partie_caract[1];
            $this->parties[ $this->partie_parsing_state ] = array();
        }
        else
            $this->parties[ $this->partie_parsing_state ][$partie_caract[0]] = $partie_caract[1];

    }

}

/* a little test */

$parser = new Parser();
$parser->parse('test.txt');

echo "======== Fight report - ".$parser->date." ==========\n\n";
echo "Victim :\n\n";
print_r($parser->victim);
echo "Parties :\n\n";
print_r($parser->parties);
echo "Items: \n\n";
print_r($parser->items);

?>

Мы можем это сделать, потому что здесь надежность и производительность не проблема :-)

Удачной игры!

person e-satis    schedule 28.11.2008

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

Некоторые строки, такие как «Отброшенные элементы:», изменяют состояние, заставляя вас интерпретировать следующие строки как элементы. Находясь в состоянии «чтение вовлеченных сторон», вы будете добавлять каждую строку в массив данных о человеке, и когда вы читаете пустую строку, вы знаете, что у вас есть полная запись.

Вот грубый FSM, который я сделал в GraphViz

Конечный автомат

Некоторые ребра вызывают действия в вашем коде, например чтение пустых строк.

person Paul Dixon    schedule 28.11.2008
comment
я не могу конкурировать с картинкой, +1 тоже за нарушение div :) - person Owen; 28.11.2008
comment
Определенно самый профессиональный ответ. Но я не уверен, что это ему сильно поможет. Это для EVE Online, а не для парсера вывода ADA... - person e-satis; 28.11.2008

Вас может заинтересовать http://pear.php.net/package/PHP_LexerGenerator.

(Да, это альфа. Да, я сам не использовал его. Да, вам нужно знать/изучить синтаксис лексера. Почему я предлагаю его? Просто любопытно, какой у вас опыт с ним ;-))

person VolkerK    schedule 28.11.2008
comment
+1. Я написал пример кода для наивного конечного автомата, но я верю, что генератор лексеров сделает эту работу намного эффективнее, если формат станет более сложным. - person e-satis; 28.11.2008
comment
Я снова и снова пишу наивные синтаксические анализаторы, а потом думаю, хм, мог бы сделать это в два раза быстрее, если бы только хотел lex, flex и т. д. чуть больше. - person VolkerK; 28.11.2008