Как сделать xml-поиск PHP simplexml для текстового значения в элементе ELEMENT с разделителями табуляции и возвращать текст из этого же элемента в другом смещении, от которого смещается текст поиска?
Допустим, я хочу найти элемент DATA, содержащий значение «2» и вернуть Академию LongValue.
Документ xml находится в следующем формате
<METADATA Resource="Property" Lookup="Area"> <COLUMNS>->fieldname *(->fieldname)-></COLUMNS> *(<DATA>->fielddata *(->fielddata)-></DATA>) </METADATA> Note: ignore spaces *() means 1 or more -> is tab chr(9)
В приведенном ниже примере элемент COLUMNS содержит три имени столбца ( LongValue , ShortValue , Value ), которые могут быть в любом порядке.
Каждый элемент DATA имеет 3 соответствующих текстовых значения с разделителями табуляции, например, первый элемент DATA ниже содержит
LongVlaue = 'Salado' ShortValue = 'Sal' Value = '5'
Вот XML-документ
<METADATA Resource="Property" Lookup="Area"> <COLUMNS> LongValue ShortValue Value </COLUMNS> <DATA> Salado Sal 5 </DATA> <DATA> Academy Aca 2 </DATA> <DATA> Rogers Rog 1 </DATA> <DATA> Bartlett Bar 4 </DATA> </METADATA>
Примечание. Элементы COLUMNS и DATA имеют текстовую вкладку, разделенную на 3 столбца, где каждый столбец начинается с закладки, за которой следует текст, а затем одна последняя вкладка в конце
Вот что я думаю:
1.) Желательно найти смещение для столбца с именем «Значение» из элемента COLUMNS, прежде чем пытаться найти соответствующий текст из элемента DATA, потому что столбец «Значение» может быть в любом порядке, однако текст в элементах DATA будет в этой последовательности.
2.) Найдите элемент DATA, содержащий текст в столбце «Значение», и верните текст из «LongValue».
Вот пример поиска по xpath, который работает некорректно, потому что он не учитывает смещение для столбца Value в элементе COLUMNS, чтобы он мог правильно найти соответствующую (правильную) позицию столбца «Значение» в Элемент DATA.
Вот код snip-it:
$xml_text = 'the xml document above'; $xml = simplexml_load_string($xml_text); //load the xml document $resource = 'Property'; //value for the Resource attribute METADATA. $lookup = 'Area'; //value for the Lookup attribute in METADATA $value = '2'; //the needle we are looking for $find = "\t" . $value . "\t"; /* adding tabs before and after the $value may be flawed, although each column starts with a tab followed by text, only the last column has the an extra tab. Not sure this would work properly if the column was in the middle, or if the ELEMENT happened to have multiple $value in the same element. */ /* Search for a specific METADATA element with matching Resource and Lookup attributes */ $node = $this->xml->xpath( "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']" ."/DATA[contains(., '{$find}')]" ); $x = explode("\t", (string) trim($node[0])); //convert the tab delimited //string to an array echo print_r($x,true); //this shows what the array would look like, //with out the trim there would be empty //first and last array elements Array ( [0] => Academy [1] => Aca [2] => 2 ) $LongValue = $x[0]; //assuming the LongValue is in the first column echo $LongValue; //this shows the LongValue retuned Academy
Спасибо за любую помощь!
Обновление … После публикации, придумал это …
//get index of 'Values' column from COLUMNS element $node = $this->xml->xpath( "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']" ."/COLUMNS"); if($node) { //array of column names $columns = explode("\t", strtolower((string) trim($node[0]))); $long_value_index = array_search('longvalue', $columns); } else { echo 'not found'; exit; }
Теперь с индексом $ это может вернуть LongValue из правильного смещения
$LongValue = $x[$long_value_index];
Есть предположения
Вы уже достаточно далеко, и вы хорошо проанализировали данные, с которыми вам нужно иметь дело. Также, как вы говорите, что хотите анализировать данные, мне очень хорошо. Единственное, что, вероятно, может немного улучшиться, – это то, что вы позаботитесь не делать слишком много сразу.
Один из способов сделать это – разделить проблему (проблемы) на более мелкие. Я покажу вам, как это работает, ставя код в несколько функций и методов. Но давайте начнем с одной функции, это поэтапно, поэтому вы можете попытаться следовать примерам, чтобы создать это.
Один из способов разделить проблемы на PHP – это использование функций. Например, напишите одну функцию для поиска в документе XML, что делает код более удобным и более разговорным:
/** * search metadata element * * * @param SimpleXMLElement $xml * @param string $resource metadata attribute * @param string $lookup metadata attribute * @param string $value search value * * @return SimpleXMLElement */ function metadata_search(SimpleXMLElement $xml, $resource, $lookup, $value) { $xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']" ."/DATA[contains(., '{$find}')]"; list($element)= $xml->xpath($xpath); return $element; }
Теперь вы можете легко найти документ, параметры названы и задокументированы. Все, что нужно, – вызвать функцию и получить возвращаемое значение:
$data = metadata_search($xml, 'Property', 'Area', 2);
Это может быть не идеальная функция, но это уже пример. Рядом с функциями вы также можете создавать объекты. Объекты – это функции, которые имеют свой собственный контекст. Вот почему эти функции называются методами, тогда они принадлежат объекту. Подобно методу xpath()
элемента SimpleXMLElement .
Если вы видите функцию выше, первым параметром является объект $xml
. На этом затем выполняется метод xpath. В конце концов, эта функция действительно создает и запускает запрос xpath на основе входных переменных.
Если бы мы могли перенести эту функцию непосредственно в объект $xml
, нам больше не нужно было передавать это значение в качестве первого параметра. Это следующий шаг, и он работает, расширяя SimpleXMLElement
. Мы просто добавляем один новый метод, который выполняет поиск, и метод почти такой же, как и выше. Мы также распространяемся от SimpleXMLElement
что означает, что мы создаем его подтип: это все, что он уже добавил, и добавляет новый метод:
class MetadataElement extends SimpleXMLElement { /** * @param string $resource metadata attribute * @param string $lookup metadata attribute * @param string $value search value * * @return SimpleXMLElement */ public function search($resource, $lookup, $value) { $xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']" ."/DATA[contains(., '{$value}')]"; list($element)= $this->xpath($xpath); return $element; } }
Чтобы добиться этого, нам нужно указать имя этого класса при загрузке XML-строки. Тогда метод поиска можно вызвать напрямую:
$xml = simplexml_load_string($xmlString, 'MetadataElement'); $data = $xml->search('Property', 'Area', 2);
Voila, теперь поиск осуществляется с помощью SimpleXMLElement!
Но что делать с этими $data
? Это всего лишь элемент XML, и он все еще содержит вкладки.
Еще хуже: контекст потерян: к какому столбцу метаданных это относится? Это твоя проблема. Поэтому нам нужно решить следующее: но как?
Честно говоря, есть много способов сделать это. Одна идея, которую я создал, – создать объект таблицы из XML на основе элемента метаданных:
list($metadata) = $xml->xpath('//METADATA[1]'); $csv = new CsvTable($metadata); echo $csv;
Даже при хорошем отладочном выходе:
+---------+----------+-----+ |LongValue|ShortValue|Value| +---------+----------+-----+ |Salado |Sal |5 | +---------+----------+-----+ |Academ |Aca |2 | +---------+----------+-----+ |Rogers |Rog |1 | +---------+----------+-----+ |Bartlett |Bar |4 | +---------+----------+-----+
Но это как-то много работает, если вы, вероятно, не владеете программирующими объектами, поэтому построение целой модели таблицы на ее собственном, может быть, немного.
Поэтому у меня возникла идея: почему бы не продолжить использование объекта XML, который вы уже используете, и изменить XML там немного, чтобы иметь его в лучшем формате для ваших целей. Из:
<METADATA Resource="Property" Lookup="Area"> <COLUMNS> LongValue ShortValue Value </COLUMNS> <DATA> Salado Sal 5 </DATA>
Для того, чтобы:
<METADATA Resource="Property" Lookup="Area" transformed="1"> <COLUMNS> LongValue ShortValue Value </COLUMNS> <DATA> <LongValue>Salado</LongValue><ShortValue>Sal</ShortValue><Value>5</Value> </DATA>
Это позволит не только выполнять поиск по определенному имени столбца, но и находить другие значения в элементе данных. Если поиск возвращает элемент $data
:
$xml = simplexml_load_string($xmlString, 'MetadataElement'); $data = $xml->search('Property', 'Area', 5); echo $data->Value; # 5 echo $data->LongValue; # Salado
Если мы оставим дополнительный атрибут с элементом метаданных, мы можем преобразовать эти элементы во время поиска. Если обнаружены некоторые данные и элемент еще не преобразован, он будет преобразован.
Поскольку все мы делаем это в методе поиска, код с использованием метода поиска не должен сильно меняться (если даже не совсем – зависит немного от подробных потребностей, которые у вас есть, я, возможно, не полностью понял их, но я думаю, что вы получить идею). Поэтому давайте это сработаем. Поскольку мы не хотим делать это все сразу, мы создаем несколько новых методов:
По пути мы также создадим методы, которые мы считаем полезными, вы заметите, что это также частично код, который вы уже написали (например, в search ()), он просто помещен в объект $xml
где он более естественно принадлежит ,
Затем, наконец, эти новые методы будут объединены в существующий метод search()
.
Поэтому, прежде всего, мы создаем вспомогательный метод для анализа этой строки с вкладками в массив. Это в основном ваш код, вам не нужна строка, отлитая перед trim
, и это единственное отличие. Поскольку эта функция нужна только внутри, мы делаем ее закрытой:
private function asExplodedString() { return explode("\t", trim($this)); }
По его названию ясно, что он делает. Он возвращает массив вложенных вкладок. Если вы помните, мы находимся внутри $xml
поэтому теперь каждый xml-элемент имеет этот метод. Если вы еще этого не понимаете, просто продолжайте, вы можете увидеть, как это работает прямо ниже, мы добавляем еще один метод в качестве помощника:
public function getParent() { list($parent) = $this->xpath('..') + array(0 => NULL); return $parent; }
Эта функция позволяет нам получить родительский элемент элемента. Это полезно, потому что, если мы найдем элемент данных, мы хотим преобразовать элемент метаданных, который является родительским. И поскольку эта функция используется в целом, я решил сделать ее общедоступной . Поэтому его можно использовать и во внешнем коде. Он решает общую проблему и, следовательно, не относится к той специфической природе, как метод взрыва.
Итак, теперь мы хотим преобразовать элемент метаданных. Это займет еще несколько строк кода, поскольку эти два вспомогательных метода выше, но, благодаря этим вещам, это не будет сложно.
Мы просто предполагаем, что элемент, вызываемый этим методом, является элементом метаданных. Мы не добавляем здесь чеки, чтобы код был небольшим. Поскольку это частная функция снова, нам даже не нужно проверять: если этот метод вызывается в неправильном элементе, ошибка была выполнена внутри самого класса, а не из внешнего кода. Это также хороший пример, почему я использую частные методы здесь, это гораздо более конкретный.
Итак, что мы делаем теперь с элементом метаданных, на самом деле довольно просто: мы извлекаем элемент столбца внутри, взрываем имена столбцов, а затем просматриваем каждый элемент данных, также взрываем данные, затем очищаем элемент данных только до добавьте к нему имена с именами столбцов. Наконец, мы добавляем атрибут, чтобы пометить преобразованный элемент:
private function transform() { $columns = $this->COLUMNS->asExplodedString(); foreach ($this->DATA as $data) { $values = $data->asExplodedString(); $data[0] = ''; # set the string of the element (make <DATA></DATA> empty) foreach ($columns as $index => $name) { $data->addChild($name, $values[$index]); } } $this['transformed'] = 1; }
Хорошо. Что теперь дает? Давайте проверим это. Для этого мы модифицируем существующую функцию поиска, чтобы вернуть преобразованный элемент данных – добавив одну строку кода:
public function search($resource, $lookup, $value) { $xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']" . "/DATA[contains(., '{$value}')]"; list($element) = $this->xpath($xpath); $element->getParent()->transform(); ################################### return $element; }
И затем мы выводим его как XML:
$data = $xml->search('Property', 'Area', 2); echo $data->asXML();
Это теперь дает следующий результат (украшен, он находится на одной линии обычно):
<DATA> <LongValue>Academ</LongValue> <ShortValue>Aca</ShortValue> <Value>2</Value> </DATA>
И давайте также проверять, что новый атрибут установлен, и все другие элементы данных этого метаданных-таблицы / блока также преобразуются:
echo $data->getParent()->asXML();
И результат (украшенный) также:
<METADATA Resource="Property" Lookup="Area" transformed="1"> <COLUMNS> LongValue ShortValue Value </COLUMNS> <DATA> <LongValue>Salado</LongValue> <ShortValue>Sal</ShortValue> <Value>5</Value> </DATA> ...
Это показывает, что код работает по назначению. Это может решить вашу проблему. Например, если вы всегда ищете номер, а другие столбцы не содержат чисел, и вам нужно искать только один блок метаданных. Однако, скорее всего, нет, поэтому функцию поиска нужно изменить для выполнения правильного поиска и преобразования внутри.
На этот раз мы снова используем $this
чтобы применить метод к конкретному элементу XML. Два новых метода: один для получения элемента Metadata на основе его атрибутов:
private function getMetadata($resource, $lookup) { $xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']"; list($metadata) = $this->xpath($xpath); return $metadata; }
И один для поиска определенного столбца элемента метаданных:
private function searchColumn($column, $value) { return $this->xpath("DATA[{$column}[contains(., '{$value}')]]"); }
Эти два метода затем используются в основном методе поиска. Он будет слегка изменен, сначала просмотрев элемент метаданных по его атрибутам. Затем будет проверено, требуется ли преобразование, а затем выполняется поиск по столбцу значения:
public function search($resource, $lookup, $value) { $metadata = $this->getMetadata($resource, $lookup); if (!$metadata['transformed']) { $metadata->transform(); } list($element) = $metadata->searchColumn('Value', $value); return $element; }
И вот теперь наконец-то новый способ поиска. Теперь он ищет только в правом столбце, и преобразование будет выполняться «на лету»:
$xml = simplexml_load_string($xmlString, 'MetadataElement'); $data = $xml->search('Property', 'Area', 2); echo $data->LongValue, "\n"; # Academ
Теперь это выглядит красиво, и похоже, что он абсолютно прост в использовании ! Вся сложность перешла в MetadataElement . И как это выглядит с первого взгляда?
/** * MetadataElement - Example for extending SimpleXMLElement * * @link http://stackoverflow.com/q/16281205/367456 */ class MetadataElement extends SimpleXMLElement { /** * @param string $resource metadata attribute * @param string $lookup metadata attribute * @param string $value search value * * @return SimpleXMLElement */ public function search($resource, $lookup, $value) { $metadata = $this->getMetadata($resource, $lookup); if (!$metadata['transformed']) { $metadata->transform(); } list($element) = $metadata->searchColumn('Value', $value); return $element; } private function getMetadata($resource, $lookup) { $xpath = "//METADATA[@Resource='{$resource}' and @Lookup='{$lookup}']"; list($metadata) = $this->xpath($xpath); return $metadata; } private function searchColumn($column, $value) { return $this->xpath("DATA[{$column}[contains(., '{$value}')]]"); } private function asExplodedString() { return explode("\t", trim($this)); } public function getParent() { list($parent) = $this->xpath('..') + array(0 => NULL); return $parent; } private function transform() { $columns = $this->COLUMNS->asExplodedString(); foreach ($this->DATA as $data) { $values = $data->asExplodedString(); $data[0] = ''; # set the string of the element (make <DATA></DATA> empty) foreach ($columns as $index => $name) { $data->addChild($name, $values[$index]); } } $this['transformed'] = 1; } }
Не так уж плохо. Многие небольшие методы, которые просто имеют некоторые небольшие строки кода, то есть (отн.), Легко следовать!
Поэтому я надеюсь, что это дает некоторое вдохновение, я знаю, что это был довольно простой текст для чтения. Повеселись!