xref: /aosp_15_r20/external/spdx-tools/rdfloader/parser2v3/parse_relationship.go (revision ba677afa8f67bb56cbc794f4d0e378e0da058e16)
1// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
2
3package parser2v3
4
5import (
6	"fmt"
7	"strings"
8
9	gordfParser "github.com/spdx/gordf/rdfloader/parser"
10	"github.com/spdx/gordf/rdfwriter"
11	"github.com/spdx/tools-golang/spdx/common"
12	"github.com/spdx/tools-golang/spdx/v2_3"
13)
14
15// parsing the relationship that exists in the rdf document.
16// Relationship is of type RefA relationType RefB.
17// parsing the relationship appends the relationship to the current document's
18// Relationships Slice.
19func (parser *rdfParser2_3) parseRelationship(triple *gordfParser.Triple) (err error) {
20	reln := v2_3.Relationship{}
21
22	reln.RefA, err = getReferenceFromURI(triple.Subject.ID)
23	if err != nil {
24		return err
25	}
26
27	currState := parser.cache[triple.Object.ID]
28	if currState == nil {
29		// there is no entry about the state of current package node.
30		// this is the first time we're seeing this node.
31		parser.cache[triple.Object.ID] = &nodeState{
32			object: reln,
33			Color:  WHITE,
34		}
35	} else if currState.Color == GREY {
36		// we have already started parsing this relationship node and we needn't parse it again.
37		return nil
38	}
39
40	// setting color of the state to grey to indicate that we've started to
41	// parse this node once.
42	parser.cache[triple.Object.ID].Color = GREY
43
44	// setting state color to black to indicate when we're done parsing this node.
45	defer func() { parser.cache[triple.Object.ID].Color = BLACK }()
46
47	for _, subTriple := range parser.nodeToTriples(triple.Object) {
48		switch subTriple.Predicate.ID {
49		case SPDX_RELATIONSHIP_TYPE:
50			// cardinality: exactly 1
51			reln.Relationship, err = getRelationshipTypeFromURI(subTriple.Object.ID)
52		case RDF_TYPE:
53			// cardinality: exactly 1
54			continue
55		case SPDX_RELATED_SPDX_ELEMENT:
56			// cardinality: exactly 1
57			// assumes: spdx-element is a uri
58			reln.RefB, err = getReferenceFromURI(subTriple.Object.ID)
59			if err != nil {
60				return err
61			}
62
63			relatedSpdxElementTriples := parser.nodeToTriples(subTriple.Object)
64			if len(relatedSpdxElementTriples) == 0 {
65				continue
66			}
67
68			typeTriples := rdfwriter.FilterTriples(relatedSpdxElementTriples, &subTriple.Object.ID, &RDF_TYPE, nil)
69			if len(typeTriples) != 1 {
70				return fmt.Errorf("expected %s to have exactly one rdf:type triple. found %d triples", subTriple.Object, len(typeTriples))
71			}
72			err = parser.parseRelatedElementFromTriple(&reln, typeTriples[0])
73			if err != nil {
74				return err
75			}
76		case RDFS_COMMENT:
77			// cardinality: max 1
78			reln.RelationshipComment = subTriple.Object.ID
79		default:
80			return fmt.Errorf("unexpected predicate id: %s", subTriple.Predicate.ID)
81		}
82		if err != nil {
83			return err
84		}
85	}
86	parser.doc.Relationships = append(parser.doc.Relationships, &reln)
87	return nil
88}
89
90func (parser *rdfParser2_3) parseRelatedElementFromTriple(reln *v2_3.Relationship, triple *gordfParser.Triple) error {
91	// iterate over relatedElement Type and check which SpdxElement it is.
92	var err error
93	switch triple.Object.ID {
94	case SPDX_FILE:
95		file, err := parser.getFileFromNode(triple.Subject)
96		if err != nil {
97			return fmt.Errorf("error setting a file: %v", err)
98		}
99		reln.RefB = common.DocElementID{
100			DocumentRefID: "",
101			ElementRefID:  file.FileSPDXIdentifier,
102		}
103
104	case SPDX_PACKAGE:
105		pkg, err := parser.getPackageFromNode(triple.Subject)
106		if err != nil {
107			return fmt.Errorf("error setting a package inside a relationship: %v", err)
108		}
109		reln.RefB = common.DocElementID{
110			DocumentRefID: "",
111			ElementRefID:  pkg.PackageSPDXIdentifier,
112		}
113
114	case SPDX_SPDX_ELEMENT:
115		// it shouldn't be associated with any other triple.
116		// it must be a uri reference.
117		reln.RefB, err = ExtractDocElementID(getLastPartOfURI(triple.Subject.ID))
118		if err != nil {
119			return err
120		}
121	default:
122		return fmt.Errorf("undefined relatedElement %s found while parsing relationship", triple.Object.ID)
123	}
124	return nil
125}
126
127// references like RefA and RefB of any relationship
128func getReferenceFromURI(uri string) (common.DocElementID, error) {
129	fragment := getLastPartOfURI(uri)
130	switch strings.ToLower(strings.TrimSpace(fragment)) {
131	case "noassertion", "none":
132		return common.DocElementID{
133			DocumentRefID: "",
134			ElementRefID:  common.ElementID(strings.ToUpper(fragment)),
135		}, nil
136	}
137	return ExtractDocElementID(fragment)
138}
139
140// note: relationshipType is case sensitive.
141func getRelationshipTypeFromURI(relnTypeURI string) (string, error) {
142	relnTypeURI = strings.TrimSpace(relnTypeURI)
143	lastPart := getLastPartOfURI(relnTypeURI)
144	if !strings.HasPrefix(lastPart, PREFIX_RELATIONSHIP_TYPE) {
145		return "", fmt.Errorf("relationshipType must start with %s. found %s", PREFIX_RELATIONSHIP_TYPE, lastPart)
146	}
147	lastPart = strings.TrimPrefix(lastPart, PREFIX_RELATIONSHIP_TYPE)
148
149	lastPart = strings.TrimSpace(lastPart)
150	for _, validRelationshipType := range AllRelationshipTypes() {
151		if lastPart == validRelationshipType {
152			return lastPart, nil
153		}
154	}
155	return "", fmt.Errorf("unknown relationshipType: '%s'", lastPart)
156}
157