Параметры строковой строки PHP для массива
Я хотел бы знать, как я мог преобразовать заданную строку в указанный массив:
Строка
all ("hi there \(option\)", (this, that), other) another
Требуемый результат (массив)
[0] => all,
[1] => Array(
[0] => "hi there \(option\)",
[1] => Array(
[0] => this,
[1] => that
),
[2] => other
),
[2] => another
Это используется для консоли, которую я создаю на PHP.
Я пытался использовать preg_match_all
, но я не знаю, как найти круглые скобки в скобках, чтобы "создавать массивы внутри массивов".
ИЗМЕНИТЬ
Все остальные символы, которые не указаны в примере, должны рассматриваться как String
.
РЕДАКТИРОВАТЬ 2
Я забыл упомянуть, что весь параметр вне круглых скобок должен быть обнаружен символом space
.
Ответы
Ответ 1
Нет никаких сомнений в том, что вы должны писать парсер, если вы строите дерево синтаксиса. Но если вам просто нужно проанализировать этот образец ввода regex
, все еще может быть инструментом:
<?php
$str = 'all, ("hi there", (these, that) , other), another';
$str = preg_replace('/\, /', ',', $str); //get rid off extra spaces
/*
* get rid off undefined constants with surrounding them with quotes
*/
$str = preg_replace('/(\w+),/', '\'$1\',', $str);
$str = preg_replace('/(\w+)\)/', '\'$1\')', $str);
$str = preg_replace('/,(\w+)/', ',\'$1\'', $str);
$str = str_replace('(', 'array(', $str);
$str = 'array('.$str.');';
echo '<pre>';
eval('$res = '.$str); //eval is evil.
print_r($res); //print the result
Демо.
Примечание: Если ввод будет неправильным, регулярное выражение, безусловно, завершится ошибкой. Я пишу это решение только в том случае, если вам нужно быстро script. Написание лексера и парсера - это трудоемкая работа, которая потребует много исследований.
Ответ 2
Обзор 10 000 футов
Вам нужно сделать это с помощью небольшого настраиваемого парсера: код принимает ввод этой формы и преобразует ее в желаемую форму.
На практике мне удобно группировать такие проблемы в одной из трех категорий, исходя из их сложности:
- Trivial: Проблемы, которые могут быть решены с помощью нескольких циклов и гуманных регулярных выражений. Эта категория соблазнительна: если вы даже немного не уверены в том, что проблема может быть решена таким образом, хорошим правилом является решение о том, что она не может.
- Легко: Проблемы, требующие создания небольшого парсера самостоятельно, но все еще достаточно просты, что не имеет смысла выводить большие пушки. Если вам нужно написать более ~ 100 строк кода, рассмотрите вопрос о переходе на следующую категорию.
- Принятые: Проблемы, для которых имеет смысл идти формально и использовать уже существующий проверенный генератор парсера¹.
Я классифицирую эту конкретную проблему как принадлежность ко второй категории, что означает, что вы можете приблизиться к ней следующим образом:
Написание небольшого парсера
Определение грамматики
Чтобы сделать это, вы должны сначала определить - по крайней мере неофициально, несколько быстрых заметок - грамматику, которую вы хотите проанализировать. Имейте в виду, что большинство грамматик определяются в какой-то момент рекурсивно. Итак, скажем, наша грамматика:
- Вход представляет собой последовательность
- Последовательность представляет собой серию рядов из нуля или более токенов
- токеном является либо слово, строка или массив
- Токены разделяются одним или несколькими пробельными символами
- Слово представляет собой последовательность буквенных символов (a-z)
- Строка представляет собой произвольную последовательность символов, заключенных в двойные кавычки
- Массив - это серия из одного или нескольких токенов, разделенных запятыми
Вы можете видеть, что у нас есть рекурсия в одном месте: последовательность может содержать массивы, а массив также определен в терминах последовательности (поэтому он может содержать больше массивов и т.д.).
Рассмотрение вопроса неофициально, как описано выше, проще, чем введение, но рассуждение о грамматиках проще, если вы сделаете это формально.
Построение лексера
С грамматикой в руке вы знаете, что нужно сломать вход в токены, чтобы он мог обрабатываться. Компонент, который принимает вход пользователя и преобразует его в отдельные фрагменты, определенные грамматикой, называется lexer. Лексеры тупые; они касаются только "внешнего внешнего вида" ввода и не пытаются проверить, что это действительно имеет смысл.
Вот простой лексер, который я написал для разбора вышеуказанной грамматики (не используйте это ни для чего важного, может содержать ошибки):
$input = 'all ("hi there", (this, that) , other) another';
$tokens = array();
$input = trim($input);
while($input) {
switch (substr($input, 0, 1)) {
case '"':
if (!preg_match('/^"([^"]*)"(.*)$/', $input, $matches)) {
die; // TODO: error: unterminated string
}
$tokens[] = array('string', $matches[1]);
$input = $matches[2];
break;
case '(':
$tokens[] = array('open', null);
$input = substr($input, 1);
break;
case ')':
$tokens[] = array('close', null);
$input = substr($input, 1);
break;
case ',':
$tokens[] = array('comma', null);
$input = substr($input, 1);
break;
default:
list($word, $input) = array_pad(
preg_split('/(?=[^a-zA-Z])/', $input, 2),
2,
null);
$tokens[] = array('word', $word);
break;
}
$input = trim($input);
}
print_r($tokens);
Построение анализатора
Сделав это, следующим шагом будет создание parser: компонент, который проверяет лексический ввод и преобразует его в желаемый формат. Парсер умный; в процессе преобразования ввода он также гарантирует, что вход хорошо сформирован правилами грамматики.
Парсеры обычно реализуются как состояния машин (также называемые конечными автоматами или конечными автоматами) и работают следующим образом:
- У анализатора есть состояние; это обычно число в соответствующем диапазоне, но каждое состояние также описывается с более удобным для человека именем.
- Существует цикл, который читает чтение лексических токенов по одному за раз. Основываясь на текущем состоянии и значении токена, анализатор может решить сделать одно или несколько из следующего:
- предпринять некоторые действия, которые влияют на его вывод
- измените его состояние на другое значение
- решите, что вход плохо сформирован и выдает ошибку.
¹ Генераторы Parser - это программы, входные данные которых являются формальной грамматикой, а выход - лексер и синтаксический анализатор, вы можете "просто добавить воду": просто расширьте код, чтобы выполнить "предпринять какое-то действие" в зависимости от типа токена; все остальное уже позаботится. Быстрый поиск по этой теме дает led PHP Lexer и Parser Generator?
Ответ 3
Насколько мне известно, проблема <круглой скобки - это класс 2 языка Хомского, а регулярные выражения эквивалентны классу класса Хомского 3, поэтому не должно быть регулярного выражения, которое решает эту проблему.
Но я недавно что-то прочитал:
Этот шаблон PCRE решает проблему круглых скобок (предположим, что параметр PCRE_EXTENDED установлен так, что пустое пространство игнорируется): \( ( (?>[^()]+) | (?R) )* \)
С разделителями и без пробелов: /\(((?>[^()]+)|(?R))*\)/
.
Это из Рекурсивные шаблоны (PCRE) - руководство по PHP.
В этом руководстве приведен пример, который решает почти ту же проблему, которую вы указали!
Вы или другие можете найти его и продолжить эту идею.
Я думаю, что лучшим решением является написать больной рекурсивный шаблон с помощью preg_match_all
. К сожалению, я не в силах сделать такое безумие!
Ответ 4
Во-первых, я хочу поблагодарить всех, кто помог мне в этом.
К сожалению, я не могу принять несколько ответов, потому что, если бы мог, я бы дал вам всех, потому что все ответы верны для разных типов этой проблемы.
В моем случае мне просто нужно было что-то простое и грязное, и, следуя ответам @palindrom и @PLB, у меня есть следующее для меня:
$str=transformEnd(transformStart($string));
$str = preg_replace('/([^\\\])\(/', '$1array(', $str);
$str = 'array('.$str.');';
eval('$res = '.$str);
print_r($res); //print the result
function transformStart($str){
$match=preg_match('/(^\(|[^\\\]\()/', $str, $positions, PREG_OFFSET_CAPTURE);
if (count($positions[0]))
$first=($positions[0][1]+1);
if ($first>1){
$start=substr($str, 0,$first);
preg_match_all("/(?:(?:\"(?:\\\\\"|[^\"])+\")|(?:'(?:\\\'|[^'])+')|(?:(?:[^\s^\,^\"^\']+)))/is",$start,$results);
if (count($results[0])){
$start=implode(",", $results[0]).",";
} else {
$start="";
}
$temp=substr($str, $first);
$str=$start.$temp;
}
return $str;
}
function transformEnd($str){
$match=preg_match('/(^\)|[^\\\]\))/', $str, $positions, PREG_OFFSET_CAPTURE);
if (($total=count($positions)) && count($positions[$total-1]))
$last=($positions[$total-1][1]+1);
if ($last==null)
$last=-1;
if ($last<strlen($str)-1){
$end=substr($str,$last+1);
preg_match_all("/(?:(?:\"(?:\\\\\"|[^\"])+\")|(?:'(?:\\\'|[^'])+')|(?:(?:[^\s^\,^\"^\']+)))/is",$end,$results);
if (count($results[0])){
$end=",".implode(",", $results[0]);
} else {
$end="";
}
$temp=substr($str, 0,$last+1);
$str=$temp.$end;
}
if ($last==-1){
$str=substr($str, 1);
}
return $str;
}
Другие ответы также полезны для тех, кто ищет лучший способ сделать это.
Еще раз спасибо вам = D.
Ответ 5
Я хочу знать, работает ли это:
- замените
(
на Array(
-
Используйте regex для запятой после слов или круглых скобок без запятой
preg_replace( '/[^,]\s+/', ',', $string )
-
eval( "\$result = Array( $string )" )
Ответ 6
Я поставлю алгоритм или псевдокод для его реализации. Надеюсь, вы сможете решить, как реализовать его в PHP:
function Parser([receives] input:string) returns Array
define Array returnValue;
for each integer i from 0 to length of input string do
charachter = ith character from input string.
if character is '('
returnValue.Add(Parser(substring of input after i)); // recursive call
else if character is '"'
returnValue.Add(substring of input from i to the next '"')
else if character is whitespace
continue
else
returnValue.Add(substring of input from i to the next space or end of input)
increment i to the index actually consumed
return returnValue
Ответ 7
если строковые значения являются фиксированными, это можно сделать следующим образом:
$ar = explode('("', $st);
$ar[1] = explode('",', $ar[1]);
$ar[1][1] = explode(',', $ar[1][1]);
$ar[1][2] = explode(')',$ar[1][1][2]);
unset($ar[1][1][2]);
$ar[2] =$ar[1][2][1];
unset($ar[1][2][1]);