public function toXml(string $outer = null) : string {
        $result = '';
        foreach($this->array as $key => $value) {
            $key = self::getStringValue($key);
            if(strncmp($key, '@', 1) === 0) {

                // skip attributes
            } else {
                $encodedTag = htmlspecialchars($outer ? $outer : $key, ENT_QUOTES);
                if(is_array($value) && (count($value) > 0) && isset($value[0])) {

                    // numeric array found => child nodes
                    $x = new XArray($value);
                    $result .= $x->toXml($key);
                    unset($x);
                } else {
                    if(is_array($value)) {

                        // attribute list found
                        $attrs = '';
                        foreach($value as $attrKey => $attrValue) {
                            $attrKey = self::getStringValue($attrKey);
                            if(strncmp($attrKey, '@', 1) === 0) {
                                $attrValue = self::getStringValue($attrValue);
                                $attrs .= ' ' . htmlspecialchars(substr($attrKey, 1), ENT_QUOTES) . '="' . htmlspecialchars($attrValue, ENT_QUOTES) . '"';
                            }
                        }
                        $x = new XArray($value);
                        $result .= '<' . $encodedTag . $attrs . '>' . $x->toXml() . '</' . $encodedTag . '>';
                        unset($x);
                    } else {
                        $value = self::getStringValue($value);
                        if($encodedTag !== '#text') {
                            $result .= '<' . $encodedTag . '>' . htmlspecialchars($value, ENT_QUOTES) . '</' . $encodedTag . '>';
                        } else {
                            $result .= htmlspecialchars($value, ENT_QUOTES);
                        }
                    }
                }
            }
        }
        return $result;
    }