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