Search code examples
phpregexshortcode

PHP - Nested Shortcode To Array


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?


Solution

  • 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;
    }
    

    Here is the result I am getting.

    (
        [0] => Array
            (
                [tag] => column
                [attributes] => Array
                    (
                    )
    
                [text] => 
                [has_children] => 1
                [children] => Array
                    (
                        [0] => Array
                            (
                                [tag] => row
                                [attributes] => Array
                                    (
                                    )
    
                                [text] => 
                                [has_children] => 1
                                [children] => Array
                                    (
                                        [0] => Array
                                            (
                                                [tag] => column
                                                [attributes] => Array
                                                    (
                                                    )
    
                                                [text] => 
                                                [has_children] => 
                                                [children] => Array
                                                    (
                                                    )
    
                                            )
    
                                        [1] => Array
                                            (
                                                [tag] => column
                                                [attributes] => Array
                                                    (
                                                    )
    
                                                [text] => 
                                                [has_children] => 
                                                [children] => Array
                                                    (
                                                    )
    
                                            )
    
                                    )
    
                            )
    
                    )
    
            )
    
    )