skip to Main Content

I have following PHP function which converts shortcode such as

[column]
    [row]
        [column][/column]
    [/row]
[/column]

To nested array

Array
(
    [0] => Array
        (
            [tag] => column
            [attributes] => Array
                (
                )

            [content] => Array
                (
                    [0] => Array
                        (
                            [tag] => row
                            [attributes] => Array
                                (
                                )

                            [content] => Array
                                (
                                    [0] => Array
                                        (
                                            [tag] => column
                                            [attributes] => Array
                                                (
                                                )

                                            [content] => 
                                        )

                                )

                        )

                )

        )

)

This works fine, if I have a single [column] as child, but if I have multiple column as child which is

[column]
    [row]
        [column][/column]
        [column][/column]
    [/row]
[/column]

Then it gives me incorrect nested array, which is

Array
(
    [0] => Array
        (
            [tag] => column
            [attributes] => Array
                (
                )

            [content] => Array
                (
                    [0] => Array
                        (
                            [tag] => row
                            [attributes] => Array
                                (
                                )

                            [content] => Array
                                (
                                    [0] => Array
                                        (
                                            [tag] => column
                                            [attributes] => Array
                                                (
                                                )

                                            [content] => 
                                        )

                                )

                        )

                )

        )

    [1] => Array
        (
            [tag] => column
            [attributes] => Array
                (
                )

            [content] => 
        )

)

Here is my PHP function

protected function shortCodeToArray($inputString)
{
    $itemArray = [];
    $openingTag = '/[(w+)(?:s+([^]]*))?](.*?)([/1]|$)/s';
    preg_match_all($openingTag, $inputString, $matches, PREG_SET_ORDER);
    foreach ($matches as $match) {
        $tagName = $match[1];
        $paramString = isset($match[2]) ? $match[2] : '';
        $content = $match[3];
        $nestedShortcodes = $this->shortCodeToArray($content);
        $itemArray[] = [
            'tag' => $tagName,
            'attributes' => $this->parseShortcodeParameters($paramString),
            'content' => is_array($nestedShortcodes) && !empty($nestedShortcodes) ? $nestedShortcodes : $content,
        ];
    }
    return $itemArray;
}

protected function parseShortcodeParameters($paramString)
{
    $params = [];
    preg_match_all('/(w+)s*=s*["']([^"']+)["']/', $paramString, $matches);
    for ($i = 0; $i < count($matches[0]); $i++) {
        $paramName = $matches[1][$i];
        $paramValue = $matches[2][$i];
        $params[$paramName] = $paramValue;
    }
    return $params;
}

Where am I going wrong here?

2

Answers


  1. Chosen as BEST ANSWER

    Here is how I ended up doing it, thanks to @Markus Zeller and from another SO post which I am unable to find now.

    // 1. Convert shortcode to XML like syntax
    $xmlString = str_replace(['[', ']'], ['<', '>'], $shortcode);
    
    // 2. Convert to xml using simple_xml_load_string
    $xml = simplexml_load_string($html, "SimpleXMLElement", LIBXML_NOCDATA);
    
    // 3. Convert to JSON
    $json = json_encode($xml);
    
    // 4. JSON to Array
    $array = json_decode($json, true);
    

    Although this worked, the xml -> json -> array created its own syntax and I wanted it in specific format with has_children and children keys, hence I am ended up using this function

    protected function xmlToArray($xml) {
        $result = [];
        $result[] = [
            'tag' => $xml->getName(),
            'attributes' => [],
            'text' => '',
            'has_children' => count($xml->children()) > 0,
            'children' => [],
        ];
        foreach ($xml->attributes() as $key => $value) {
            $result[0]['attributes'][$key] = (string)$value;
        }
        $children = $xml->children();
        if (count($children) === 0) {
            $result[0]['text'] = (string)$xml;
            return $result;
        }
        foreach ($children as $child) {
            $childArray = $this->xmlToArray($child);
            $result[0]['children'][] = $childArray[0];
        }
        return $result;
    }
    

  2. What about the idea to convert it to HTML tags and use DOM Parser?

    $codes = <<<'CODES'
    [column]
        [row]
            [column][/column]
            [column][/column]
        [/row]
    [/column]
    CODES;
    $html = str_replace(['[', ']'], ['<', '>'], $codes);
    libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    $dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    echo $dom->saveHTML();
    

    Outputting

    <column>
        <row>
            <column></column>
            <column></column>
        </row>
    </column>
    

    Now you can traverse the DOMDocument like you want and even manipulate in a simple way.

    foreach($dom->getElementsByTagName('row') as $row) {
        foreach($row->getElementsByTagName('column') as $column) {
            $column->appendChild($dom->createTextNode('test'));
            $column->setAttribute('class', 'test');
        }
    }
    
    echo $dom->saveHTML();
    
    <column>
        <row>
            <column class="test">test</column>
            <column class="test">test</column>
        </row>
    </column>
    

    Update

    When having it as DOM Document you can traverse the whole tree and convert to an array.

    See Demo: https://3v4l.org/h1eMR

    function getTags($element, $tags = [])
    {
        $tag = ['tagName' => $element->tagName];
    
        if ($element->hasAttributes()) {
            foreach ($element->attributes as $attribute) {
                $tag['attributes'][$attribute->name] = $attribute->value;
            }
        }
    
        if ('' !== ($nodeValue = trim($element->textContent)) && false === $element->hasChildNodes()) {
            $tag['nodeValue'] = $nodeValue;
        }
    
        if ($element->hasChildNodes()) {
            foreach ($element->childNodes as $childElement) {
                if ($childElement->nodeType !== XML_ELEMENT_NODE) {
                    continue;
                }
                $tag[] = getTags($childElement, $tags);
            }
        }
        $tags[] = $tag;
    
        return $tags;
    }
    
    $tags = getTags($dom->documentElement);
    echo var_export($tags, true);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search