public function loadFromXML($filename, $container = null, $validate = null)
    {
        if ($container === null) {
            $container = $this->root;
        } else {
            // ensure container belongs to this UI
            $found = false;
            $object = $container;
            while ($object !== null) {
                if ($object === $this->root) {
                    $found = true;
                    break;
                }
                $object = $object->parent;
            }
            if (!$found) {
                throw new SwatException(
                    'Cannot load a UI tree into a container that is not part ' .
                        'of this SwatUI. If you need to load a UI tree into ' .
                        'another SwatUI you should specify the root container ' .
                        'when constructing this SwatUI.'
                );
            }
        }

        if ($validate === null) {
            $validate = self::$validate_mode;
        }

        $xml_file = null;

        if (file_exists($filename)) {
            $xml_file = $filename;
        }

        // try to guess the translation callback based on the
        // filename of the xml
        $class_map_reversed = array_reverse(self::$class_map, true);
        foreach ($class_map_reversed as $prefix => $path) {
            if (
                mb_strpos($xml_file, $prefix) !== false &&
                is_callable(array($prefix, 'gettext'))
            ) {
                $this->translation_callback = array($prefix, 'gettext');
            }
        }

        // fall back to the global gettext function if no package-specific
        // one was found
        if (
            $this->translation_callback === null &&
            extension_loaded('gettext')
        ) {
            $this->translation_callback = 'gettext';
        }

        if ($xml_file === null) {
            throw new SwatFileNotFoundException(
                "SwatML file not found: '{$filename}'.",
                0,
                $xml_file
            );
        }

        // External entity loader is disabled for loading the SwatUI file to
        // prevent local and remote file inclusion attacks. See
        // https://phpsecurity.readthedocs.io/en/latest/Injection-Attacks.html
        $external_entity_loader = libxml_disable_entity_loader(false);
        $errors = libxml_use_internal_errors(true);

        // Use PHP's file loader rather than libxml so it will work with
        // the entity loader disabled.
        $xml = file_get_contents($xml_file);
        if ($xml === false) {
            throw new SwatException(
                "Unable to load SwatML file: '{$filename}'.",
                0
            );
        }

        $document = new DOMDocument();
        $document->loadXML($xml);
        if ($validate) {
            // check for pear installed version first and if not found, fall
            // back to version in svn
            $schema_file = '@DATA-DIR@/Swat/system/swatml.rng';
            if (!file_exists($schema_file)) {
                $schema_file = __DIR__ . '/../system/swatml.rng';
            }

            libxml_disable_entity_loader(false);
            $document->relaxNGValidate($schema_file);
        }

        $xml_errors = libxml_get_errors();
        libxml_clear_errors();
        libxml_use_internal_errors($errors);
        libxml_disable_entity_loader($external_entity_loader);

        if (count($xml_errors) > 0) {
            $message = '';
            foreach ($xml_errors as $error) {
                $message .= sprintf(
                    "%s in %s, line %d\n",
                    trim($error->message),
                    $error->file,
                    $error->line
                );
            }

            throw new SwatInvalidSwatMLException(
                "Invalid SwatML:\n" . $message,
                0,
                $xml_file
            );
        }

        $this->parseUI($document->documentElement, $container);
    }