spdx_tools.spdx.graph_generation

 1# SPDX-FileCopyrightText: 2023 spdx contributors
 2#
 3# SPDX-License-Identifier: Apache-2.0
 4from beartype.typing import Dict, List, Union
 5
 6from spdx_tools.spdx.model import File, Package, Snippet
 7
 8try:
 9    from networkx import DiGraph
10except ImportError:
11    DiGraph = None
12from spdx_tools.spdx.document_utils import get_contained_spdx_elements
13from spdx_tools.spdx.model import Document, Relationship
14
15
16def export_graph_from_document(document: Document, file_name: str) -> None:
17    from networkx.drawing import nx_agraph
18
19    graph = generate_relationship_graph_from_spdx(document)
20    _color_nodes(graph)
21    attributes_graph = nx_agraph.to_agraph(graph)  # convert to a pygraphviz graph
22    attributes_graph.draw(file_name, prog="dot")
23
24
25def generate_relationship_graph_from_spdx(document: Document) -> DiGraph:
26    from networkx import DiGraph
27
28    graph = DiGraph()
29    graph.add_node(document.creation_info.spdx_id, element=document.creation_info)
30
31    contained_elements: Dict[str, Union[Package, File, Snippet]] = get_contained_spdx_elements(document)
32    contained_element_nodes = [(spdx_id, {"element": element}) for spdx_id, element in contained_elements.items()]
33    graph.add_nodes_from(contained_element_nodes)
34
35    relationships_by_spdx_id: Dict[str, List[Relationship]] = dict()
36    for relationship in document.relationships:
37        relationships_by_spdx_id.setdefault(relationship.spdx_element_id, []).append(relationship)
38
39    for spdx_id, relationships in relationships_by_spdx_id.items():
40        if spdx_id not in graph.nodes():
41            # this will add any external spdx_id to the graph where we have no further information about the element,
42            # to indicate that this node represents an element we add the attribute "element"
43            graph.add_node(spdx_id, element=None)
44        for relationship in relationships:
45            relationship_node_key = relationship.spdx_element_id + "_" + relationship.relationship_type.name
46            graph.add_node(relationship_node_key, comment=relationship.comment)
47            graph.add_edge(relationship.spdx_element_id, relationship_node_key)
48            # if the related spdx element is SpdxNone or SpdxNoAssertion we need a type conversion
49            related_spdx_element_id = str(relationship.related_spdx_element_id)
50
51            if related_spdx_element_id not in graph.nodes():
52                # this will add any external spdx_id to the graph where we have no further information about
53                # the element, to indicate that this node represents an element we add the attribute "element"
54                graph.add_node(
55                    related_spdx_element_id,
56                    element=None,
57                )
58            graph.add_edge(relationship_node_key, related_spdx_element_id)
59
60    return graph
61
62
63def _color_nodes(graph: DiGraph) -> None:
64    for node in graph.nodes():
65        if "_" in node:
66            # nodes representing a RelationshipType are concatenated with the spdx_element_id,
67            # to only see the RelationshipType when rendering the graph to a picture we add
68            # a label to these nodes
69            graph.add_node(node, color="lightgreen", label=node.split("_", 1)[-1])
70        elif node == "SPDXRef-DOCUMENT":
71            graph.add_node(node, color="indianred2")
72        else:
73            graph.add_node(node, color="lightskyblue")
def export_graph_from_document( document: spdx_tools.spdx.model.document.Document, file_name: str) -> None:
17def export_graph_from_document(document: Document, file_name: str) -> None:
18    from networkx.drawing import nx_agraph
19
20    graph = generate_relationship_graph_from_spdx(document)
21    _color_nodes(graph)
22    attributes_graph = nx_agraph.to_agraph(graph)  # convert to a pygraphviz graph
23    attributes_graph.draw(file_name, prog="dot")
def generate_relationship_graph_from_spdx( document: spdx_tools.spdx.model.document.Document) -> networkx.classes.digraph.DiGraph:
26def generate_relationship_graph_from_spdx(document: Document) -> DiGraph:
27    from networkx import DiGraph
28
29    graph = DiGraph()
30    graph.add_node(document.creation_info.spdx_id, element=document.creation_info)
31
32    contained_elements: Dict[str, Union[Package, File, Snippet]] = get_contained_spdx_elements(document)
33    contained_element_nodes = [(spdx_id, {"element": element}) for spdx_id, element in contained_elements.items()]
34    graph.add_nodes_from(contained_element_nodes)
35
36    relationships_by_spdx_id: Dict[str, List[Relationship]] = dict()
37    for relationship in document.relationships:
38        relationships_by_spdx_id.setdefault(relationship.spdx_element_id, []).append(relationship)
39
40    for spdx_id, relationships in relationships_by_spdx_id.items():
41        if spdx_id not in graph.nodes():
42            # this will add any external spdx_id to the graph where we have no further information about the element,
43            # to indicate that this node represents an element we add the attribute "element"
44            graph.add_node(spdx_id, element=None)
45        for relationship in relationships:
46            relationship_node_key = relationship.spdx_element_id + "_" + relationship.relationship_type.name
47            graph.add_node(relationship_node_key, comment=relationship.comment)
48            graph.add_edge(relationship.spdx_element_id, relationship_node_key)
49            # if the related spdx element is SpdxNone or SpdxNoAssertion we need a type conversion
50            related_spdx_element_id = str(relationship.related_spdx_element_id)
51
52            if related_spdx_element_id not in graph.nodes():
53                # this will add any external spdx_id to the graph where we have no further information about
54                # the element, to indicate that this node represents an element we add the attribute "element"
55                graph.add_node(
56                    related_spdx_element_id,
57                    element=None,
58                )
59            graph.add_edge(relationship_node_key, related_spdx_element_id)
60
61    return graph