(self)
return content
Return a markdown string summarizing detected differences.
Return a markdown string summarizing detected differences.
Return a markdown string summarizing detected differences .
def get_markdown_string(self):
        """
        Return a markdown string summarizing detected differences.
        """
        
        def getSectionList(items, obj_type, section):
            """
            parse a list of items in a section and return a string for the items
            """
            
            # get parents which have children
            childless = list(filter(lambda item: not self.has_subtechniques(item, True) and not ("x_mitre_is_subtechnique" in item and item["x_mitre_is_subtechnique"]), items))
            parents = list(filter(lambda item: self.has_subtechniques(item, True) and not ("x_mitre_is_subtechnique" in item and item["x_mitre_is_subtechnique"]), items))
            children = { item["id"]: item for item in filter(lambda item: "x_mitre_is_subtechnique" in item and item["x_mitre_is_subtechnique"], items) }

            subtechnique_of_rels = self.new_subtechnique_of_rels if section != "deletions" else self.old_subtechnique_of_rels
            id_to_technique = self.new_id_to_technique if section != "deletions" else self.old_id_to_technique

            parentToChildren = {} # stixID => [ children ]
            for relationship in subtechnique_of_rels:
                if relationship["target_ref"] in parentToChildren:
                    if relationship["source_ref"] in children:
                        parentToChildren[relationship["target_ref"]].append(children[relationship["source_ref"]])
                else:
                    if relationship["source_ref"] in children:
                        parentToChildren[relationship["target_ref"]] = children[relationship["source_ref"]]
                        parentToChildren[relationship["target_ref"]] = [ children[relationship["source_ref"]] ]


            # now group parents and children
            groupings = []

            for parent in childless + parents:
                parent_children = parentToChildren.pop(parent["id"]) if parent["id"] in parentToChildren else []
                groupings.append({
                    "parent": parent,
                    "parentInSection": True,
                    "children": parent_children
                })

            for parentID in parentToChildren:
                groupings.append({
                    "parent": id_to_technique[parentID],
                    "parentInSection": False,
                    "children": parentToChildren[parentID]
                })
            
            groupings = sorted(groupings, key=lambda grouping: grouping["parent"]["name"])
            
            def placard(item):
                """get a section list item for the given SDO according to section type"""
                if section == "revocations":
                    revoker = item['revoked_by']
                    if "x_mitre_is_subtechnique" in revoker and revoker["x_mitre_is_subtechnique"]:
                        # get revoking technique's parent for display
                        parentID = list(filter(lambda rel: rel["source_ref"] == revoker["id"], subtechnique_of_rels))[0]["target_ref"]
                        parentName = id_to_technique[parentID]["name"] if parentID in id_to_technique else "ERROR NO PARENT"
                        return f"{item['name']} (revoked by { parentName}: [{revoker['name']}]({self.site_prefix}/{self.getUrlFromStix(revoker, True)}))"
                    else:
                        return f"{item['name']} (revoked by [{revoker['name']}]({self.site_prefix}/{self.getUrlFromStix(revoker)}))"
                if section == "deletions":
                    return f"{item['name']}"
                else:
                    is_subtechnique = item["type"] == "attack-pattern" and "x_mitre_is_subtechnique" in item and item["x_mitre_is_subtechnique"]
                    return f"[{item['name']}]({self.site_prefix}/{self.getUrlFromStix(item, is_subtechnique)})"


            # build sectionList string
            sectionString = ""
            for grouping in groupings:
                if grouping["parentInSection"]:
                    sectionString += f"* { placard(grouping['parent']) }\n"
                # else:
                #     sectionString += f"* _{grouping['parent']['name']}_\n"
                for child in sorted(grouping["children"], key=lambda child: child["name"]):
                    if grouping["parentInSection"]:
                        sectionString += f"\t* {placard(child) }\n"
                    else:
                        sectionString += f"* { grouping['parent']['name'] }: { placard(child) }\n"

            return sectionString

        self.verboseprint("generating markdown string... ", end="", flush="true")

        content = ""
        for obj_type in self.data.keys():
            domains = ""
            for domain in self.data[obj_type]:
                domain_sections = ""
                for section in self.data[obj_type][domain]:
                    if len(self.data[obj_type][domain][section]) > 0: # if there are items in the section
                        section_items = getSectionList(self.data[obj_type][domain][section], obj_type, section)
                    else: # no items in section
                        section_items = "No changes"
                    header = sectionNameToSectionHeaders[section] + ":"
                    if "{obj_type}" in header:
                        if section == "additions":
                            header = header.replace("{obj_type}", attackTypeToPlural[obj_type].capitalize())
                        else: header = header.replace("{obj_type}", obj_type.capitalize())
                    if section_items == "No changes":
                        domain_sections += f"{header}\n{section_items}\n\n" # e.g "added techniques:"
                    else: domain_sections += f"{header}\n\n{section_items}\n\n" # add empty line between header and section list
                domains += f"**{domainToDomainLabel[domain]}**\n\n{domain_sections}" # e.g "enterprise"
            content += f"### {attackTypeToPlural[obj_type].capitalize()}\n\n{domains}" # e.g "techniques"

        if self.show_key:
            key_content = self.get_md_key()
            content = f"{key_content}\n\n{content}"

        self.verboseprint("done")

        return content
def get_markdown_string ( self ) : def getSectionList ( items , obj_type , section ) : """
            parse a list of items in a section and return a string for the items
            """ # get parents which have children childless = list ( filter ( lambda item : not self . has_subtechniques ( item , True ) and not ( "x_mitre_is_subtechnique" in item and item [ "x_mitre_is_subtechnique" ] ) , items ) ) parents = list ( filter ( lambda item : self . has_subtechniques ( item , True ) and not ( "x_mitre_is_subtechnique" in item and item [ "x_mitre_is_subtechnique" ] ) , items ) ) children = { item [ "id" ] : item for item in filter ( lambda item : "x_mitre_is_subtechnique" in item and item [ "x_mitre_is_subtechnique" ] , items ) } subtechnique_of_rels = self . new_subtechnique_of_rels if section != "deletions" else self . old_subtechnique_of_rels id_to_technique = self . new_id_to_technique if section != "deletions" else self . old_id_to_technique parentToChildren = { } # stixID => [ children ] for relationship in subtechnique_of_rels : if relationship [ "target_ref" ] in parentToChildren : if relationship [ "source_ref" ] in children : parentToChildren [ relationship [ "target_ref" ] ] . append ( children [ relationship [ "source_ref" ] ] ) else : if relationship [ "source_ref" ] in children : parentToChildren [ relationship [ "target_ref" ] ] = children [ relationship [ "source_ref" ] ] parentToChildren [ relationship [ "target_ref" ] ] = [ children [ relationship [ "source_ref" ] ] ] # now group parents and children groupings = [ ] for parent in childless + parents : parent_children = parentToChildren . pop ( parent [ "id" ] ) if parent [ "id" ] in parentToChildren else [ ] groupings . append ( { "parent" : parent , "parentInSection" : True , "children" : parent_children } ) for parentID in parentToChildren : groupings . append ( { "parent" : id_to_technique [ parentID ] , "parentInSection" : False , "children" : parentToChildren [ parentID ] } ) groupings = sorted ( groupings , key = lambda grouping : grouping [ "parent" ] [ "name" ] ) def placard ( item ) : """get a section list item for the given SDO according to section type""" if section == "revocations" : revoker = item [ 'revoked_by' ] if "x_mitre_is_subtechnique" in revoker and revoker [ "x_mitre_is_subtechnique" ] : # get revoking technique's parent for display parentID = list ( filter ( lambda rel : rel [ "source_ref" ] == revoker [ "id" ] , subtechnique_of_rels ) ) [ 0 ] [ "target_ref" ] parentName = id_to_technique [ parentID ] [ "name" ] if parentID in id_to_technique else "ERROR NO PARENT" return f"{item['name']} (revoked by { parentName}: [{revoker['name']}]({self.site_prefix}/{self.getUrlFromStix(revoker, True)}))" else : return f"{item['name']} (revoked by [{revoker['name']}]({self.site_prefix}/{self.getUrlFromStix(revoker)}))" if section == "deletions" : return f"{item['name']}" else : is_subtechnique = item [ "type" ] == "attack-pattern" and "x_mitre_is_subtechnique" in item and item [ "x_mitre_is_subtechnique" ] return f"[{item['name']}]({self.site_prefix}/{self.getUrlFromStix(item, is_subtechnique)})" # build sectionList string sectionString = "" for grouping in groupings : if grouping [ "parentInSection" ] : sectionString += f"* { placard(grouping['parent']) }\n" # else: #     sectionString += f"* _{grouping['parent']['name']}_\n" for child in sorted ( grouping [ "children" ] , key = lambda child : child [ "name" ] ) : if grouping [ "parentInSection" ] : sectionString += f"\t* {placard(child) }\n" else : sectionString += f"* { grouping['parent']['name'] }: { placard(child) }\n" return sectionString self . verboseprint ( "generating markdown string... " , end = "" , flush = "true" ) content = "" for obj_type in self . data . keys ( ) : domains = "" for domain in self . data [ obj_type ] : domain_sections = "" for section in self . data [ obj_type ] [ domain ] : if len ( self . data [ obj_type ] [ domain ] [ section ] ) > 0 : # if there are items in the section section_items = getSectionList ( self . data [ obj_type ] [ domain ] [ section ] , obj_type , section ) else : # no items in section section_items = "No changes" header = sectionNameToSectionHeaders [ section ] + ":" if "{obj_type}" in header : if section == "additions" : header = header . replace ( "{obj_type}" , attackTypeToPlural [ obj_type ] . capitalize ( ) ) else : header = header . replace ( "{obj_type}" , obj_type . capitalize ( ) ) if section_items == "No changes" : domain_sections += f"{header}\n{section_items}\n\n" # e.g "added techniques:" else : domain_sections += f"{header}\n\n{section_items}\n\n" # add empty line between header and section list domains += f"**{domainToDomainLabel[domain]}**\n\n{domain_sections}" # e.g "enterprise" content += f"### {attackTypeToPlural[obj_type].capitalize()}\n\n{domains}" # e.g "techniques" if self . show_key : key_content = self . get_md_key ( ) content = f"{key_content}\n\n{content}" self . verboseprint ( "done" ) return content