Esempio n. 1
0
    def _parse_discussion(self, res, res_type):
        """
        Parses discussion content.
        """

        namespaces = {
            "imsdt_xmlv1p1":
            "http://www.imsglobal.org/xsd/imsccv1p1/imsdt_v1p1",
            "imsdt_xmlv1p2":
            "http://www.imsglobal.org/xsd/imsccv1p2/imsdt_v1p2",
            "imsdt_xmlv1p3":
            "http://www.imsglobal.org/xsd/imsccv1p3/imsdt_v1p3",
        }

        data = {"dependencies": []}
        for child in res["children"]:
            if isinstance(child, ResourceFile):
                tree = filesystem.get_xml_tree(self._res_filename(child.href))
                root = tree.getroot()
                ns = {"dt": namespaces[res_type]}
                data["title"] = root.find("dt:title", ns).text
                data["text"] = root.find("dt:text", ns).text
            elif isinstance(child, ResourceDependency):
                data["dependencies"].append(
                    self.get_resource_content(child.identifierref))
        return data
Esempio n. 2
0
    def load_manifest_extracted(self):
        manifest = self._extract()

        # load module_meta
        self.is_canvas_flavor = self._check_if_canvas_flavor()
        if self.is_canvas_flavor:
            self.module_meta = self._load_module_meta()

        tree = filesystem.get_xml_tree(manifest)
        root = tree.getroot()
        self._update_namespaces(root)
        data = self._parse_manifest(root)
        self.metadata = data["metadata"]
        self.organizations = data["organizations"]
        self.resources = data["resources"]
        self.resources_by_id = {r["identifier"]: r for r in self.resources}

        # Keep a map with href -> identifier mapping. Used when processing statics.
        self.resource_id_by_href = {
            r["href"]: r["identifier"]
            for r in self.resources if "href" in r
        }

        self.version = self.metadata.get("schema",
                                         {}).get("version", self.version)
        return data
Esempio n. 3
0
    def _parse_lti(self, resource):
        """
        Parses LTI resource.
        """

        tree = filesystem.get_xml_tree(
            self._res_filename(resource["children"][0].href))
        root = tree.getroot()
        ns = {
            "blti": "http://www.imsglobal.org/xsd/imsbasiclti_v1p0",
            "lticp": "http://www.imsglobal.org/xsd/imslticp_v1p0",
            "lticm": "http://www.imsglobal.org/xsd/imslticm_v1p0",
        }
        title = root.find("blti:title", ns).text
        description = root.find("blti:description", ns).text
        launch_url = root.find("blti:secure_launch_url", ns)
        if launch_url is None:
            launch_url = root.find("blti:launch_url", ns)
        if launch_url is not None:
            launch_url = launch_url.text
        else:
            launch_url = ""
        width = root.find(
            "blti:extensions/lticm:property[@name='selection_width']", ns)
        if width is None:
            width = "500"
        else:
            width = width.text
        height = root.find(
            "blti:extensions/lticm:property[@name='selection_height']", ns)
        if height is None:
            height = "500"
        else:
            height = height.text
        custom = root.find("blti:custom", ns)
        if custom is None:
            parameters = dict()
        else:
            parameters = {option.get("name"): option.text for option in custom}
        # For Canvas flavored CC, tool_id can be used as lti_id if present
        tool_id = root.find("blti:extensions/lticm:property[@name='tool_id']",
                            ns)
        if tool_id is None:
            # Create a simple slug lti_id from title
            lti_id = simple_slug(title)
        else:
            lti_id = tool_id.text
        data = {
            "title": title,
            "description": description,
            "launch_url": launch_url,
            "height": height,
            "width": width,
            "custom_parameters": parameters,
            "lti_id": lti_id,
        }
        return data
Esempio n. 4
0
    def get_resource_content(self, identifier):
        """
        Get the resource named by `identifier`.

        If the resource can be retrieved, returns a tuple: the first element
        indicates the type of content, either "html" or "link".  The second
        element is a dict with details, which vary by the type.

        If the resource can't be retrieved, returns a tuple of None, None.

        """
        res = self.resources_by_id.get(identifier)
        if res is None:
            print("*** Missing resource: {}".format(identifier))
            return None, None

        res_type = res["type"]
        if res_type == "webcontent":
            res_filename = self._res_filename(res["children"][0].href)
            if res_filename.suffix == ".html":
                try:
                    with open(str(res_filename)) as res_file:
                        html = res_file.read()
                except:  # noqa: E722
                    print("Failure reading {!r} from id {}".format(
                        res_filename, identifier))
                    raise
                return "html", {"html": html}
            else:
                print("*** Skipping webcontent: {}".format(res_filename))
                return None, None
        elif res_type == "imswl_xmlv1p1":
            tree = filesystem.get_xml_tree(
                self._res_filename(res["children"][0].href))
            root = tree.getroot()
            ns = {"wl": "http://www.imsglobal.org/xsd/imsccv1p1/imswl_v1p1"}
            title = root.find("wl:title", ns).text
            url = root.find("wl:url", ns).get("href")
            return "link", {"href": url, "text": title}
        elif res_type == "imsbasiclti_xmlv1p0":
            data = self._parse_lti(res)
            return "lti", data
        elif res_type == "imsqti_xmlv1p2/imscc_xmlv1p1/assessment":
            res_filename = self._res_filename(res['children'][0].href)
            qti_parser = QtiParser(res_filename)
            return "qti", qti_parser.parse_qti()
        elif res_type == "imsdt_xmlv1p1":
            data = self._parse_discussion(res)
            return "discussion", data
        else:
            text = "Unimported content: type = {!r}".format(res_type)
            if "href" in res:
                text += ", href = {!r}".format(res["href"])
            print("***", text)
            return "html", {"html": text}
Esempio n. 5
0
 def load_manifest_extracted(self):
     manifest = self._extract()
     tree = filesystem.get_xml_tree(manifest)
     root = tree.getroot()
     self._update_namespaces(root)
     data = self.parse_manifest(root)
     self.metadata = data['metadata']
     self.organizations = data['organizations']
     self.resources = data['resources']
     self.version = self.metadata.get('schema',
                                      {}).get('version', self.version)
     return data
Esempio n. 6
0
 def _parse_discussion(self, res):
     data = {'dependencies': []}
     for child in res['children']:
         if isinstance(child, ResourceFile):
             tree = filesystem.get_xml_tree(self._res_filename(child.href))
             root = tree.getroot()
             ns = {
                 "dt": "http://www.imsglobal.org/xsd/imsccv1p1/imsdt_v1p1"
             }
             data["title"] = root.find("dt:title", ns).text
             data["text"] = root.find("dt:text", ns).text
         elif isinstance(child, ResourceDependency):
             data['dependencies'].append(
                 self.get_resource_content(child.identifierref))
     return data
Esempio n. 7
0
    def _parse_lti(self, resource):
        """
        Parses resource of ``imsbasiclti_xmlv1p0`` type.
        """

        tree = filesystem.get_xml_tree(
            self._res_filename(resource['children'][0].href))
        root = tree.getroot()
        ns = {
            'blti': 'http://www.imsglobal.org/xsd/imsbasiclti_v1p0',
            'lticp': 'http://www.imsglobal.org/xsd/imslticp_v1p0',
            'lticm': 'http://www.imsglobal.org/xsd/imslticm_v1p0',
        }
        title = root.find('blti:title', ns).text
        description = root.find('blti:description', ns).text
        launch_url = root.find('blti:secure_launch_url', ns)
        if launch_url is None:
            launch_url = root.find('blti:launch_url', ns)
        if launch_url is not None:
            launch_url = launch_url.text
        else:
            launch_url = ''
        width = root.find(
            "blti:extensions/lticm:property[@name='selection_width']", ns)
        if width is None:
            width = '500'
        else:
            width = width.text
        height = root.find(
            "blti:extensions/lticm:property[@name='selection_height']", ns)
        if height is None:
            height = '500'
        else:
            height = height.text
        custom = root.find('blti:custom', ns)
        if custom is None:
            parameters = dict()
        else:
            parameters = {option.get('name'): option.text for option in custom}
        data = {
            'title': title,
            'description': description,
            'launch_url': launch_url,
            'height': height,
            'width': width,
            'custom_parameters': parameters,
        }
        return data
Esempio n. 8
0
    def _parse_lti(self, resource):
        """
        Parses resource of ``imsbasiclti_xmlv1p0`` type.
        """

        tree = filesystem.get_xml_tree(
            self._res_filename(resource["children"][0].href))
        root = tree.getroot()
        ns = {
            "blti": "http://www.imsglobal.org/xsd/imsbasiclti_v1p0",
            "lticp": "http://www.imsglobal.org/xsd/imslticp_v1p0",
            "lticm": "http://www.imsglobal.org/xsd/imslticm_v1p0",
        }
        title = root.find("blti:title", ns).text
        description = root.find("blti:description", ns).text
        launch_url = root.find("blti:secure_launch_url", ns)
        if launch_url is None:
            launch_url = root.find("blti:launch_url", ns)
        if launch_url is not None:
            launch_url = launch_url.text
        else:
            launch_url = ""
        width = root.find(
            "blti:extensions/lticm:property[@name='selection_width']", ns)
        if width is None:
            width = "500"
        else:
            width = width.text
        height = root.find(
            "blti:extensions/lticm:property[@name='selection_height']", ns)
        if height is None:
            height = "500"
        else:
            height = height.text
        custom = root.find("blti:custom", ns)
        if custom is None:
            parameters = dict()
        else:
            parameters = {option.get("name"): option.text for option in custom}
        data = {
            "title": title,
            "description": description,
            "launch_url": launch_url,
            "height": height,
            "width": width,
            "custom_parameters": parameters,
        }
        return data
Esempio n. 9
0
 def _load_module_meta(self):
     """
     Load module meta from course settings if exists
     """
     module_meta_path = self.directory / COURSE_SETTINGS_DIR / MODULE_META
     tree = filesystem.get_xml_tree(module_meta_path)
     module_meta = {}
     if tree:
         root = tree.getroot()
         items = root.findall(".//{*}item")
         for item in items:
             if item.attrib.get("identifier"):
                 module_meta[item.attrib["identifier"]] = {
                     "content_type": item.find("./{*}content_type").text,
                 }
     return module_meta
Esempio n. 10
0
    def parse_qti(self):
        """
        Parses resource of ``imsqti_xmlv1p2/imscc_xmlv1p1/assessment`` type.
        """

        tree = filesystem.get_xml_tree(self.resource_filename)
        root = tree.getroot()

        # qti xml can contain multiple problems represented by <item/> elements
        problems = root.findall(
            ".//qti:section[@ident='root_section']/qti:item", self.NS)

        parsed_problems = []

        for problem in problems:
            data = {}

            attributes = problem.attrib

            data['ident'] = attributes['ident']
            data['title'] = attributes['title']

            cc_profile = self._parse_problem_profile(problem)
            data['cc_profile'] = cc_profile

            parse_problem = self._problem_parsers_map.get(cc_profile)

            if parse_problem is None:
                raise QtiError("Unknown cc_profile: \"{}\"".format(cc_profile))

            try:
                data.update(parse_problem(problem))
                parsed_problems.append(data)
            except NotImplementedError:
                logger.info("Problem with ID %s can\'t be converted.",
                            problem.attrib.get('ident'))
                logger.info("    Profile %s is not supported.", cc_profile)
                logger.info("    At file %s.", self.resource_filename)

        return parsed_problems
Esempio n. 11
0
    def get_resource_content(self, identifier):
        """
        Get the resource named by `identifier`.

        If the resource can be retrieved, returns a tuple: the first element
        indicates the type of content, either "html" or "link".  The second
        element is a dict with details, which vary by the type.

        If the resource can't be retrieved, returns a tuple of None, None.

        """
        res = self.resources_by_id.get(identifier)
        if res is None and self.is_canvas_flavor:
            res = self.resources_by_id.get(
                self.module_meta.get_identifierref(identifier))
        if res is None:
            logger.info("Missing resource: %s", identifier)
            return None, None

        res_type = res["type"]

        if res_type == "webcontent":
            res_relative_path = res["children"][0].href
            res_filename = self._res_filename(res_relative_path)
            if res_filename.suffix == ".html":
                try:
                    with open(str(res_filename)) as res_file:
                        html = res_file.read()
                except:  # noqa: E722
                    logger.error("Failure reading %s from id %s", res_filename,
                                 identifier)  # noqa: E722
                    raise
                return "html", {"html": html}
            elif "web_resources" in str(res_filename) and imghdr.what(
                    str(res_filename)):
                static_filename = str(res_filename).split("web_resources/")[1]
                olx_static_path = "/{}/{}".format(OLX_STATIC_DIR,
                                                  static_filename)
                html = (
                    '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
                    '</head><body><p><img src="{}" alt="{}"></p></body></html>'
                    .format(olx_static_path, static_filename))
                return "html", {"html": html}
            elif "web_resources" not in str(res_filename):
                # This webcontent is outside of ``web_resources`` directory
                # So we need to manually copy it to OLX_STATIC_DIR
                self.extra_static_files.append(res_relative_path)
                olx_static_path = "/{}/{}".format(OLX_STATIC_DIR,
                                                  res_relative_path)
                html = (
                    '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
                    '</head><body><p><a href="{}" alt="{}">{}<a></p></body></html>'
                    .format(olx_static_path, res_relative_path,
                            res_relative_path))
                return "html", {"html": html}
            else:
                logger.info("Skipping webcontent: %s", res_filename)
                return None, None

        # Match any of imswl_xmlv1p1, imswl_xmlv1p2 etc
        elif re.match(r"^imswl_xmlv\d+p\d+$", res_type):
            tree = filesystem.get_xml_tree(
                self._res_filename(res["children"][0].href))
            root = tree.getroot()
            namespaces = {
                "imswl_xmlv1p1":
                "http://www.imsglobal.org/xsd/imsccv1p1/imswl_v1p1",
                "imswl_xmlv1p2":
                "http://www.imsglobal.org/xsd/imsccv1p2/imswl_v1p2",
                "imswl_xmlv1p3":
                "http://www.imsglobal.org/xsd/imsccv1p3/imswl_v1p3",
            }
            ns = {"wl": namespaces[res_type]}
            title = root.find("wl:title", ns).text
            url = root.find("wl:url", ns).get("href")
            return "link", {"href": url, "text": title}

        # Match any of imsbasiclti_xmlv1p0, imsbasiclti_xmlv1p3 etc
        elif re.match(r"^imsbasiclti_xmlv\d+p\d+$", res_type):
            data = self._parse_lti(res)
            # Canvas flavored courses have correct url in module meta for lti links
            if self.is_canvas_flavor:
                item_data = self.module_meta.get_external_tool_item_data(
                    identifier)
                if item_data:
                    data["launch_url"] = item_data.get("url",
                                                       data["launch_url"])
            return "lti", data

        # Match any of imsqti_xmlv1p2/imscc_xmlv1p1/assessment, imsqti_xmlv1p3/imscc_xmlv1p3/assessment etc
        elif re.match(r"^imsqti_xmlv\d+p\d+/imscc_xmlv\d+p\d+/assessment$",
                      res_type):
            res_filename = self._res_filename(res["children"][0].href)
            qti_parser = QtiParser(res_filename)
            return "qti", qti_parser.parse_qti()

        # Match any of imsdt_xmlv1p1, imsdt_xmlv1p2, imsdt_xmlv1p3 etc
        elif re.match(r"^imsdt_xmlv\d+p\d+$", res_type):
            data = self._parse_discussion(res, res_type)
            return "discussion", data

        else:
            text = f"Unimported content: type = {res_type!r}"
            if "href" in res:
                text += ", href = {!r}".format(res["href"])
            logger.info("%s", text)
            return "html", {"html": text}