За последние 24 часа нас посетили 22816 программистов и 1284 робота. Сейчас ищут 955 программистов ...

Обработка xml файлов

Тема в разделе "PHP для профи", создана пользователем webheader, 23 июн 2016.

  1. webheader

    webheader Активный пользователь

    С нами с:
    7 окт 2013
    Сообщения:
    15
    Симпатии:
    0
    Не уверен, что вопрос нужно задавать в разделе для профи, но на мой взгляд вопрос не для новичков.

    Есть задача прочитать и обработать xml файлы (предположительно запуская скрипт по cron).
    Файлы от 10 до 150 мб (возможно будут и больше). С самим чтением и обработкой вопросов нет, все достаточно просто.

    НО! Я не укладываюсь в лимит времени. Понимаю, что можно увеличить время в php.ini или просто в php файле, но хотелось бы обойтись без этого.

    Собственно вопрос, как это сделать?
     
  2. denis01

    denis01 Суперстар
    Команда форума Модератор

    С нами с:
    9 дек 2014
    Сообщения:
    12.230
    Симпатии:
    1.715
    Адрес:
    Молдова, г.Кишинёв
    Как это решено на данный момент? Какой подход и библиотеки/классы/функции используются?
    Банальный поиск решений по словам php read big xml files производился?
     
  3. webheader

    webheader Активный пользователь

    С нами с:
    7 окт 2013
    Сообщения:
    15
    Симпатии:
    0
    Для теста, попробовал воспользоваться этим классом.
    Код (Text):
    1. <?php
    2.  
    3. /**
    4. * Simple XML Reader
    5. *
    6. * @license Public Domain
    7. * @author Dmitry Pyatkov(aka dkrnl) <dkrnl@yandex.ru>
    8. * @url http://github.com/dkrnl/SimpleXMLReader
    9. */
    10. class SimpleXMLReader extends XMLReader
    11. {
    12.  
    13.     /**
    14.      * Callbacks
    15.      *
    16.      * @var array
    17.      */
    18.     protected $callback = array();
    19.  
    20.  
    21.     /**
    22.      * Depth
    23.      *
    24.      * @var int
    25.      */
    26.     protected $currentDepth = 0;
    27.  
    28.  
    29.     /**
    30.      * Previos depth
    31.      *
    32.      * @var int
    33.      */
    34.     protected $prevDepth = 0;
    35.  
    36.  
    37.     /**
    38.      * Stack of the parsed nodes
    39.      *
    40.      * @var array
    41.      */
    42.     protected $nodesParsed = array();
    43.  
    44.  
    45.     /**
    46.      * Stack of the node types
    47.      *
    48.      * @var array
    49.      */
    50.     protected $nodesType = array();
    51.  
    52.  
    53.     /**
    54.      * Stack of node position
    55.      *
    56.      * @var array
    57.      */
    58.     protected $nodesCounter = array();
    59.  
    60.  
    61.     /**
    62.      * Add node callback
    63.      *
    64.      * @param  string   $xpath
    65.      * @param  callback $callback
    66.      * @param  integer  $nodeType
    67.      * @return SimpleXMLReader
    68.      */
    69.     public function registerCallback($xpath, $callback, $nodeType = XMLREADER::ELEMENT)
    70.     {
    71.         if (isset($this->callback[$nodeType][$xpath])) {
    72.             throw new Exception("Already exists callback '$xpath':$nodeType.");
    73.         }
    74.         if (!is_callable($callback)) {
    75.             throw new Exception("Not callable callback '$xpath':$nodeType.");
    76.         }
    77.         $this->callback[$nodeType][$xpath] = $callback;
    78.         return $this;
    79.     }
    80.  
    81.  
    82.     /**
    83.      * Remove node callback
    84.      *
    85.      * @param  string  $xpath
    86.      * @param  integer $nodeType
    87.      * @return SimpleXMLReader
    88.      */
    89.     public function unRegisterCallback($xpath, $nodeType = XMLREADER::ELEMENT)
    90.     {
    91.         if (!isset($this->callback[$nodeType][$xpath])) {
    92.             throw new Exception("Unknow parser callback '$xpath':$nodeType.");
    93.         }
    94.         unset($this->callback[$nodeType][$xpath]);
    95.         return $this;
    96.     }
    97.  
    98.     /**
    99.      * Moves cursor to the next node in the document.
    100.      *
    101.      * @link https://php.ru/manual/xmlreader.read.html
    102.      * @return bool Returns TRUE on success or FALSE on failure.
    103.      */
    104.     public function read()
    105.     {
    106.         $read = parent::read();      
    107.         if ($this->depth < $this->prevDepth) {
    108.             if (!isset($this->nodesParsed[$this->depth])) {
    109.                 throw new Exception("Invalid xml: missing items in SimpleXMLReader::\$nodesParsed");
    110.             }
    111.             if (!isset($this->nodesCounter[$this->depth])) {
    112.                 throw new Exception("Invalid xml: missing items in SimpleXMLReader::\$nodesCounter");
    113.             }
    114.             if (!isset($this->nodesType[$this->depth])) {
    115.                 throw new Exception("Invalid xml: missing items in SimpleXMLReader::\$nodesType");
    116.             }
    117.             $this->nodesParsed = array_slice($this->nodesParsed, 0, $this->depth + 1, true);
    118.             $this->nodesCounter = array_slice($this->nodesCounter, 0, $this->depth + 1, true);
    119.             $this->nodesType = array_slice($this->nodesType, 0, $this->depth + 1, true);
    120.         }
    121.         if (isset($this->nodesParsed[$this->depth]) && $this->localName == $this->nodesParsed[$this->depth] && $this->nodeType == $this->nodesType[$this->depth]) {
    122.             $this->nodesCounter[$this->depth] = $this->nodesCounter[$this->depth] + 1;
    123.         } else {
    124.             $this->nodesParsed[$this->depth] = $this->localName;
    125.             $this->nodesType[$this->depth] = $this->nodeType;
    126.             $this->nodesCounter[$this->depth] = 1;
    127.         }
    128.         $this->prevDepth = $this->depth;      
    129.         return $read;
    130.     }
    131.  
    132.     /**
    133.      * Return current xpath node
    134.      *
    135.      * @param boolean $nodesCounter
    136.      * @return string
    137.      */
    138.      public function currentXpath($nodesCounter = false)
    139.      {
    140.         if (count($this->nodesCounter) != count($this->nodesParsed) && count($this->nodesCounter) != count($this->nodesType)) {
    141.             throw new Exception("Empty reader");
    142.         }
    143.         $result = "";
    144.         foreach ($this->nodesParsed as $depth => $name) {
    145.             switch ($this->nodesType[$depth]) {
    146.                 case self::ELEMENT:
    147.                     $result .= "/" . $name;
    148.                     if ($nodesCounter) {
    149.                         $result .= "[" . $this->nodesCounter[$depth] . "]";
    150.                     }
    151.                     break;
    152.  
    153.                 case self::TEXT:
    154.                 case self::CDATA:
    155.                     $result .= "/text()";
    156.                     break;
    157.  
    158.                 case self::COMMENT:
    159.                     $result .= "/comment()";
    160.                     break;
    161.  
    162.                 case self::ATTRIBUTE:
    163.                     $result .= "[@{$name}]";
    164.                     break;
    165.             }
    166.         }
    167.         return $result;
    168.     }
    169.  
    170.  
    171.     /**
    172.      * Run parser
    173.      *
    174.      * @return void
    175.      */
    176.     public function parse()
    177.     {
    178.         if (empty($this->callback)) {
    179.             throw new Exception("Empty parser callback.");
    180.         }
    181.         $continue = true;
    182.         while ($continue && $this->read()) {
    183.             if (!isset($this->callback[$this->nodeType])) {
    184.                 continue;
    185.             }
    186.             if (isset($this->callback[$this->nodeType][$this->name])) {
    187.                 $continue = call_user_func($this->callback[$this->nodeType][$this->name], $this);
    188.             } else {
    189.                 $xpath = $this->currentXpath(false); // without node counter
    190.                 if (isset($this->callback[$this->nodeType][$xpath])) {
    191.                     $continue = call_user_func($this->callback[$this->nodeType][$xpath], $this);
    192.                 } else {
    193.                     $xpath = $this->currentXpath(true); // with node counter
    194.                     if (isset($this->callback[$this->nodeType][$xpath])) {
    195.                         $continue = call_user_func($this->callback[$this->nodeType][$xpath], $this);
    196.                     }
    197.                 }
    198.             }
    199.         }
    200.     }
    201.  
    202.     /**
    203.      * Run XPath query on current node
    204.      *
    205.      * @param  string $path
    206.      * @param  string $version
    207.      * @param  string $encoding
    208.      * @param  string $className
    209.      * @return array(SimpleXMLElement)
    210.      */
    211.     public function expandXpath($path, $version = "1.0", $encoding = "UTF-8", $className = null)
    212.     {    
    213.         return $this->expandSimpleXml($version, $encoding, $className)->xpath($path);
    214.     }
    215.  
    216.     /**
    217.      * Expand current node to string
    218.      *
    219.      * @param  string $version
    220.      * @param  string $encoding
    221.      * @param  string $className
    222.      * @return SimpleXMLElement
    223.      */
    224.     public function expandString($version = "1.0", $encoding = "UTF-8", $className = null)
    225.     {
    226.         return $this->expandSimpleXml($version, $encoding, $className)->asXML();
    227.     }
    228.  
    229.     /**
    230.      * Expand current node to SimpleXMLElement
    231.      *
    232.      * @param  string $version
    233.      * @param  string $encoding
    234.      * @param  string $className
    235.      * @return SimpleXMLElement
    236.      */
    237.     public function expandSimpleXml($version = "1.0", $encoding = "UTF-8", $className = null)
    238.     {
    239.         $element = $this->expand();
    240.         $document = new DomDocument($version, $encoding);
    241.         if ($element instanceof DOMCharacterData) {
    242.             $nodeName = array_splice($this->nodesParsed, -2, 1);
    243.             $nodeName = (isset($nodeName[0]) && $nodeName[0] ? $nodeName[0] : "root");
    244.             $node = $document->createElement($nodeName);
    245.             $node->appendChild($element);
    246.             $element = $node;
    247.         }
    248.         $node = $document->importNode($element, true);
    249.         $document->appendChild($node);
    250.         return simplexml_import_dom($node, $className);
    251.     }
    252.  
    253.     /**
    254.      * Expand current node to DomDocument
    255.      *
    256.      * @param  string $version
    257.      * @param  string $encoding
    258.      * @return DomDocument
    259.      */
    260.     public function expandDomDocument($version = "1.0", $encoding = "UTF-8")
    261.     {
    262.         $element = $this->expand();
    263.         $document = new DomDocument($version, $encoding);
    264.         if ($element instanceof DOMCharacterData) {
    265.             $nodeName = array_splice($this->nodesParsed, -2, 1);
    266.             $nodeName = (isset($nodeName[0]) && $nodeName[0] ? $nodeName[0] : "root");
    267.             $node = $document->createElement($nodeName);
    268.             $node->appendChild($element);
    269.             $element = $node;
    270.         }
    271.         $node = $document->importNode($element, true);
    272.         $document->appendChild($node);
    273.         return $document;
    274.     }
    275.  
    276. }
     
  4. denis01

    denis01 Суперстар
    Команда форума Модератор

    С нами с:
    9 дек 2014
    Сообщения:
    12.230
    Симпатии:
    1.715
    Адрес:
    Молдова, г.Кишинёв
    Какое в этом коде узкое место? Где больше всего тратиться времени?
     
  5. webheader

    webheader Активный пользователь

    С нами с:
    7 окт 2013
    Сообщения:
    15
    Симпатии:
    0
    Этот момент не тестил, но уверен, что на больших файлах в любом случае упрусь в лимит времени, в связи с этим вопрос, что делать дальше?
     
  6. denis01

    denis01 Суперстар
    Команда форума Модератор

    С нами с:
    9 дек 2014
    Сообщения:
    12.230
    Симпатии:
    1.715
    Адрес:
    Молдова, г.Кишинёв
    можно поискать в интернете как замерить скорость частей кода, сделать профайлинг, простой или с xdebug.
    Надо выяснить где узкое горлышко. Понять алгоритм и переделать на тот что меньше времени будет тратить.

    Нужно только читать файл?
    Попробуй поискать готовые классы для работы с xml, если лень самому выяснять всё.
     
  7. webheader

    webheader Активный пользователь

    С нами с:
    7 окт 2013
    Сообщения:
    15
    Симпатии:
    0
    Замерить скорость выполнения скрипта не сложно, это понятно.
    Замерю скорость, сейчас на 10 мб (на локалке), времени не хватает.
     
  8. denis01

    denis01 Суперстар
    Команда форума Модератор

    С нами с:
    9 дек 2014
    Сообщения:
    12.230
    Симпатии:
    1.715
    Адрес:
    Молдова, г.Кишинёв
    Чтение фалов состоит из многих частей, вот нужно понять где ты тратишь много времени, чтобы другой алгоритм поискать, который будет меньше тратить времени, но например хранить фал в оперативной памяти, сильнее грузить процессор или что-то другое делать.
     
  9. mr.akv

    mr.akv Активный пользователь

    С нами с:
    31 мар 2015
    Сообщения:
    1.604
    Симпатии:
    206
    У меня сейчас задача парсинга больших csv-файлов. В итоге я пришёл к выводу, что одним PHP я не обойдусь. В принципе, я думаю, можно как-то сделать, но у меня планируются и файлы по 600 метров. В итоге я решил использовать AJAX, который будет запускать скрипт, передавать в скрипт начальную позицию для чтения, определённое количество строк, и по завершению отработки скрипта будет заново его запускать с другой позицией