protected function parseElement($name, $callbacks, LocatorInterface $locator)
    {
        $hasChildren = !$this->isEmptyElement;
        $attributes  = $this->hasAttributes ? $this->getAttributes() : [];
        $children    = [];

        $callback = 'onBefore' . ucfirst($name);

        if (isset($callbacks[$callback])) {
            $attributes = call_user_func($callbacks[$callback], $attributes);
        }

        if ($hasChildren) {
            while ($this->read()) {
                $currentName = $this->name;

                if ($this->nodeType == \XMLReader::END_ELEMENT && $currentName == $name) {
                    break;
                }

                if ($this->nodeType == \XMLReader::ELEMENT) {
                    $child = $this->parseElement($currentName, $callbacks, $locator);

                    if (isset($child->name)) {
                        $children[$currentName][$child->name] = $child;
                    } else {
                        $children[$currentName][] = $child;
                    }
                } elseif ($currentName[0] == '#') {
                    $text = trim($this->value);

                    if (!empty($text)) {
                        $children[$currentName] = $text;
                    }
                } else {
                    throw new InvalidElementException("Unknown element {$currentName} => {$this->value}");
                }
            }
        }

        $className = __NAMESPACE__ . '\\' . ucfirst($name);

        if (!class_exists($className)) {
            $className = __NAMESPACE__ . '\\Element';
        }

        $element = new $className(array_merge($attributes, $children));

        $callback = 'onAfter' . ucfirst($name);

        if (isset($callbacks[$callback])) {
            call_user_func($callbacks[$callback], $element, $locator);
        }

        return $element;
    }