когда подстрока найдена, создайте элемент DOM -php - PullRequest
1 голос
/ 19 марта 2012

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

$str="hi there! [1], how are you? [2]";

желаемый результат:

<sentence>
hi there! <child1>1</child1>, how are you? <child2>2</child2>
</sentence>

я использую php dom -> $dom = new DOMDocument('1.0'); ...

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

        $root= $dom->createElement('sentence', null);
        $root= $dom->appendChild($root);
        $root->setAttribute('attr-1', 'value-1');

Я использовал несколько подходов, например, и некоторые с preg-split;

$counter=1;
$pos = preg_match('/\[([1-9][0-9]*)\]/', $str);
    if ($pos == true) {
    $substr=$dom->createElement('child', $counter);
    $root->appendChild($substr);
    $counter++;
    }

Я знаю, что код не достоин, но, опять же, показать, что это не удовольствие ..

любая помощь приветствуется ..

Ответы [ 2 ]

3 голосов
/ 19 марта 2012

Ваш оригинальный код не так уж далеко. Однако вам нужно, чтобы регулярное выражение также соответствовало тексту, который вы хотите добавить (и для этого вам нужен текстовый узел). После каждого матча вам также нужно сдвигать смещение, где продолжать матч:

$str = "hi there! [1], how are you? [2]";

$dom = new DOMDocument('1.0');
$root= $dom->createElement('sentence', null);
$root= $dom->appendChild($root);
$root->setAttribute('attr-1', 'value-1'); # ...

$counter = 0;
$offset = 0;
while ($pos = preg_match('/(.*?)\[([1-9][0-9]*)\]/', $str, $matches, NULL, $offset)) {
    list(, $text, $number) = $matches;
    if (strlen($text)) {
        $root->appendChild($dom->createTextNode($text));
    }
    if (strlen($number)) {
        $counter++;
        $root->appendChild($dom->createElement("child$counter", $number));

    }
    $offset += strlen($matches[0]);
}

Петля while сравнима с if, которую вы имели, просто превращая ее в петлю. Кроме того, текстовые узлы добавляются, если какой-либо текст соответствует (например, в вашей строке может быть [1] [2], поэтому текст будет пустым. Вывод этого примера:

<?xml version="1.0"?>
<sentence attr-1="value-1">
  hi there! <child1>1</child1>, how are you? <child2>2</child2>
</sentence>

Редактировать Поработав немного, я пришел к выводу, что вы можете разделить проблему. Одна часть - это анализ строки, а другая - фактическая вставка узлов (например, textnode для текста и elementnode, если это число). Начиная сзади, это сразу выглядит практичным, вторая часть первая:

$dom = new DOMDocument('1.0');
$root = $dom->createElement('sentence', null);
$root = $dom->appendChild($root);
$root->setAttribute('attr-1', 'value-1'); # ...

$str = "hi there! [1], how are you? [2] test";

$it = new Tokenizer($str);
$counter = 0;
foreach ($it as $type => $string) {
    switch ($type) {
        case Tokenizer::TEXT:
            $root->appendChild($dom->createTextNode($string));
            break;

        case Tokenizer::NUMBER:
            $counter++;
            $root->appendChild($dom->createElement("child$counter", $string));
            break;

        default:
            throw new Exception(sprintf('Invalid type %s.', $type));
    }
}

echo $dom->saveXML();

В этом примере мы вообще не заботимся о разборе. Мы получаем либо текст, либо число ($type), и мы можем принять решение о вставке текстового узла или элемента. Таким образом, как бы разбор строки не выполнялся, этот код всегда будет работать. Если с ним возникнет проблема (например, $counter больше не будет интересен), это не будет иметь ничего общего с разбором / токенизацией строки.

Сам синтаксический анализ был заключен в Iterator, называемый Tokenizer. Он содержит все, чтобы разбить строку на текстовые и числовые элементы. Он имеет дело со всеми деталями, например, что происходит, если после последнего числа есть текст и так далее:

class Tokenizer implements Iterator
{
    const TEXT = 1;
    const NUMBER = 2;
    private $offset;
    private $string;
    private $fetched;

    public function __construct($string)
    {
        $this->string = $string;
    }

    public function rewind()
    {
        $this->offset = 0;
        $this->fetch();
    }

    private function fetch()
    {
        if ($this->offset >= strlen($this->string)) {
            return;
        }
        $result = preg_match('/\[([1-9][0-9]*)\]/', $this->string, $matches, PREG_OFFSET_CAPTURE, $this->offset);
        if (!$result) {
            $this->fetched[] = array(self::TEXT, substr($this->string, $this->offset));
            $this->offset = strlen($this->string);
            return;
        }
        $pos = $matches[0][1];
        if ($pos != $this->offset) {
            $this->fetched[] = array(self::TEXT, substr($this->string, $this->offset, $pos - $this->offset));
        }
        $this->fetched[] = array(self::NUMBER, $matches[1][0]);
        $this->offset = $pos + strlen($matches[0][0]);
    }

    public function current()
    {
        list(, $current) = current($this->fetched);
        return $current;
    }

    public function key()
    {
        list($key) = current($this->fetched);
        return $key;
    }

    public function next()
    {
        array_shift($this->fetched);
        if (!$this->fetched) $this->fetch();
    }

    public function valid()
    {
        return (bool)$this->fetched;
    }
}

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

Опять этот пример выводит XML в конце, так что здесь это пример. Обратите внимание, что я добавил текст после последнего элемента:

<?xml version="1.0"?>
<sentence attr-1="value-1">
  hi there! <child1>1</child1>, how are you? <child2>2</child2> test
</sentence>
0 голосов
/ 19 марта 2012

Сначала выполните замену регулярными выражениями, а затем проанализируйте документ.

$xml = preg_replace('/\[(\d+)\]/', '<child$1>$1</child$1>', $str);
$doc = new DOMDocument('1.0');
$doc->loadXML("<sentence>$xml</sentence>");

Вот демонстрационная версия.

...