Added tests and parsing for rest of Package section's own data

Signed-off-by: Steve Winslow <swinslow@gmail.com>
diff --git a/v0/spdx/file.go b/v0/spdx/file.go
index c16af47..cbd1470 100644
--- a/v0/spdx/file.go
+++ b/v0/spdx/file.go
@@ -19,7 +19,9 @@
 
 	// 4.4: File Checksum: may have keys for SHA1, SHA256 and/or MD5
 	// Cardinality: mandatory, one SHA1, others may be optionally provided
-	FileChecksum map[string]string
+	FileChecksumSHA1   string
+	FileChecksumSHA256 string
+	FileChecksumMD5    string
 
 	// 4.5: Concluded License: SPDX License Expression, "NONE" or "NOASSERTION"
 	// Cardinality: mandatory, one
diff --git a/v0/spdx/package.go b/v0/spdx/package.go
index 11c6189..d011e36 100644
--- a/v0/spdx/package.go
+++ b/v0/spdx/package.go
@@ -61,7 +61,9 @@
 
 	// 3.10: Package Checksum: may have keys for SHA1, SHA256 and/or MD5
 	// Cardinality: optional, one or many
-	PackageChecksum map[string]string
+	PackageChecksumSHA1   string
+	PackageChecksumSHA256 string
+	PackageChecksumMD5    string
 
 	// 3.11: Package Home Page
 	// Cardinality: optional, one
@@ -106,7 +108,7 @@
 
 	// 3.21: Package External Reference
 	// Cardinality: optional, one or many
-	PackageExternalReferences []PackageExternalReference2_1
+	PackageExternalReferences []*PackageExternalReference2_1
 
 	// 3.22: Package External Reference Comment
 	// Cardinality: conditional (optional, one) for each External Reference
diff --git a/v0/tvloader/parser2_1/parse_package.go b/v0/tvloader/parser2_1/parse_package.go
index e69c187..f5dc62a 100644
--- a/v0/tvloader/parser2_1/parse_package.go
+++ b/v0/tvloader/parser2_1/parse_package.go
@@ -10,6 +10,12 @@
 )
 
 func (parser *tvParser2_1) parsePairFromPackage2_1(tag string, value string) error {
+	// expire pkgExtRef for anything other than a comment
+	// (we'll actually handle the comment further below)
+	if tag != "ExternalRefComment" {
+		parser.pkgExtRef = nil
+	}
+
 	switch tag {
 	case "PackageName":
 		// if package already has a name, create and go on to a new package
@@ -84,6 +90,58 @@
 		code, excludesFileName := extractCodeAndExcludes(value)
 		parser.pkg.PackageVerificationCode = code
 		parser.pkg.PackageVerificationCodeExcludedFile = excludesFileName
+	case "PackageChecksum":
+		subkey, subvalue, err := extractSubs(value)
+		if err != nil {
+			return err
+		}
+		switch subkey {
+		case "SHA1":
+			parser.pkg.PackageChecksumSHA1 = subvalue
+		case "SHA256":
+			parser.pkg.PackageChecksumSHA256 = subvalue
+		case "MD5":
+			parser.pkg.PackageChecksumMD5 = subvalue
+		default:
+			return fmt.Errorf("got unknown checksum type %s", subkey)
+		}
+	case "PackageHomePage":
+		parser.pkg.PackageHomePage = value
+	case "PackageSourceInfo":
+		parser.pkg.PackageSourceInfo = value
+	case "PackageLicenseConcluded":
+		parser.pkg.PackageLicenseConcluded = value
+	case "PackageLicenseInfoFromFiles":
+		parser.pkg.PackageLicenseInfoFromFiles = append(parser.pkg.PackageLicenseInfoFromFiles, value)
+	case "PackageLicenseDeclared":
+		parser.pkg.PackageLicenseDeclared = value
+	case "PackageLicenseComments":
+		parser.pkg.PackageLicenseComments = value
+	case "PackageCopyrightText":
+		parser.pkg.PackageCopyrightText = value
+	case "PackageSummary":
+		parser.pkg.PackageSummary = value
+	case "PackageDescription":
+		parser.pkg.PackageDescription = value
+	case "PackageComment":
+		parser.pkg.PackageComment = value
+	case "ExternalRef":
+		parser.pkgExtRef = &spdx.PackageExternalReference2_1{}
+		parser.pkg.PackageExternalReferences = append(parser.pkg.PackageExternalReferences, parser.pkgExtRef)
+		category, refType, locator, err := extractPackageExternalReference(value)
+		if err != nil {
+			return err
+		}
+		parser.pkgExtRef.Category = category
+		parser.pkgExtRef.RefType = refType
+		parser.pkgExtRef.Locator = locator
+	case "ExternalRefComment":
+		if parser.pkgExtRef == nil {
+			return fmt.Errorf("no current ExternalRef found")
+		}
+		parser.pkgExtRef.ExternalRefComment = value
+		// now, expire pkgExtRef anyway because it can have at most one comment
+		parser.pkgExtRef = nil
 	}
 
 	return nil
@@ -107,3 +165,20 @@
 	fileName := strings.TrimSpace(parsedSp[0])
 	return code, fileName
 }
+
+func extractPackageExternalReference(value string) (string, string, string, error) {
+	sp := strings.Split(value, " ")
+	// remove any that are just whitespace
+	keepSp := []string{}
+	for _, s := range sp {
+		ss := strings.TrimSpace(s)
+		if ss != "" {
+			keepSp = append(keepSp, ss)
+		}
+	}
+	// now, should have 3 items and should be able to map them
+	if len(keepSp) != 3 {
+		return "", "", "", fmt.Errorf("expected 3 elements, got %d", len(keepSp))
+	}
+	return keepSp[0], keepSp[1], keepSp[2], nil
+}
diff --git a/v0/tvloader/parser2_1/parse_package_test.go b/v0/tvloader/parser2_1/parse_package_test.go
index db78911..404fb14 100644
--- a/v0/tvloader/parser2_1/parse_package_test.go
+++ b/v0/tvloader/parser2_1/parse_package_test.go
@@ -215,6 +215,224 @@
 
 	// Package Verification Code
 	// SKIP -- separate tests for "excludes", or not, below
+
+	// Package Checksums
+	codeSha1 := "85ed0817af83a24ad8da68c2b5094de69833983c"
+	sumSha1 := "SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c"
+	codeSha256 := "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"
+	sumSha256 := "SHA256: 11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"
+	codeMd5 := "624c1abb3664f4b35547e7c73864ad24"
+	sumMd5 := "MD5: 624c1abb3664f4b35547e7c73864ad24"
+	err = parser.parsePairFromPackage2_1("PackageChecksum", sumSha1)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	err = parser.parsePairFromPackage2_1("PackageChecksum", sumSha256)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	err = parser.parsePairFromPackage2_1("PackageChecksum", sumMd5)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageChecksumSHA1 != codeSha1 {
+		t.Errorf("expected %s for PackageChecksumSHA1, got %s", codeSha1, parser.pkg.PackageChecksumSHA1)
+	}
+	if parser.pkg.PackageChecksumSHA256 != codeSha256 {
+		t.Errorf("expected %s for PackageChecksumSHA256, got %s", codeSha256, parser.pkg.PackageChecksumSHA256)
+	}
+	if parser.pkg.PackageChecksumMD5 != codeMd5 {
+		t.Errorf("expected %s for PackageChecksumMD5, got %s", codeMd5, parser.pkg.PackageChecksumMD5)
+	}
+
+	// Package Home Page
+	err = parser.parsePairFromPackage2_1("PackageHomePage", "https://example.com/whatever2")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageHomePage != "https://example.com/whatever2" {
+		t.Errorf("got %v for PackageHomePage", parser.pkg.PackageHomePage)
+	}
+
+	// Package Source Info
+	err = parser.parsePairFromPackage2_1("PackageSourceInfo", "random comment")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageSourceInfo != "random comment" {
+		t.Errorf("got %v for PackageSourceInfo", parser.pkg.PackageSourceInfo)
+	}
+
+	// Package License Concluded
+	err = parser.parsePairFromPackage2_1("PackageLicenseConcluded", "Apache-2.0 OR GPL-2.0-or-later")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageLicenseConcluded != "Apache-2.0 OR GPL-2.0-or-later" {
+		t.Errorf("got %v for PackageLicenseConcluded", parser.pkg.PackageLicenseConcluded)
+	}
+
+	// All Licenses Info From Files
+	lics := []string{
+		"Apache-2.0",
+		"GPL-2.0-or-later",
+		"CC0-1.0",
+	}
+	for _, lic := range lics {
+		err = parser.parsePairFromPackage2_1("PackageLicenseInfoFromFiles", lic)
+		if err != nil {
+			t.Errorf("expected nil error, got %v", err)
+		}
+	}
+	for _, licWant := range lics {
+		flagFound := false
+		for _, licCheck := range parser.pkg.PackageLicenseInfoFromFiles {
+			if licWant == licCheck {
+				flagFound = true
+			}
+		}
+		if flagFound == false {
+			t.Errorf("didn't find %s in PackageLicenseInfoFromFiles", licWant)
+		}
+	}
+	if len(lics) != len(parser.pkg.PackageLicenseInfoFromFiles) {
+		t.Errorf("expected %d licenses in PackageLicenseInfoFromFiles, got %d", len(lics),
+			len(parser.pkg.PackageLicenseInfoFromFiles))
+	}
+
+	// Package License Declared
+	err = parser.parsePairFromPackage2_1("PackageLicenseDeclared", "Apache-2.0 OR GPL-2.0-or-later")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageLicenseDeclared != "Apache-2.0 OR GPL-2.0-or-later" {
+		t.Errorf("got %v for PackageLicenseDeclared", parser.pkg.PackageLicenseDeclared)
+	}
+
+	// Package License Comments
+	err = parser.parsePairFromPackage2_1("PackageLicenseComments", "this is a license comment")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageLicenseComments != "this is a license comment" {
+		t.Errorf("got %v for PackageLicenseComments", parser.pkg.PackageLicenseComments)
+	}
+
+	// Package Copyright Text
+	err = parser.parsePairFromPackage2_1("PackageCopyrightText", "Copyright (c) me myself and i")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageCopyrightText != "Copyright (c) me myself and i" {
+		t.Errorf("got %v for PackageCopyrightText", parser.pkg.PackageCopyrightText)
+	}
+
+	// Package Summary
+	err = parser.parsePairFromPackage2_1("PackageSummary", "i wrote this package")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageSummary != "i wrote this package" {
+		t.Errorf("got %v for PackageSummary", parser.pkg.PackageSummary)
+	}
+
+	// Package Description
+	err = parser.parsePairFromPackage2_1("PackageDescription", "i wrote this package a lot")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageDescription != "i wrote this package a lot" {
+		t.Errorf("got %v for PackageDescription", parser.pkg.PackageDescription)
+	}
+
+	// Package Comment
+	err = parser.parsePairFromPackage2_1("PackageComment", "i scanned this package")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkg.PackageComment != "i scanned this package" {
+		t.Errorf("got %v for PackageComment", parser.pkg.PackageComment)
+	}
+
+	// Package External References and Comments
+	ref1 := "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+	ref1Category := "SECURITY"
+	ref1Type := "cpe23Type"
+	ref1Locator := "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+	ref1Comment := "this is comment #1"
+	ref2 := "OTHER LocationRef-acmeforge acmecorp/acmenator/4.1.3alpha"
+	ref2Category := "OTHER"
+	ref2Type := "LocationRef-acmeforge"
+	ref2Locator := "acmecorp/acmenator/4.1.3alpha"
+	ref2Comment := "this is comment #2"
+	err = parser.parsePairFromPackage2_1("ExternalRef", ref1)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if len(parser.pkg.PackageExternalReferences) != 1 {
+		t.Errorf("expected 1 external reference, got %d", len(parser.pkg.PackageExternalReferences))
+	}
+	if parser.pkgExtRef == nil {
+		t.Errorf("expected non-nil pkgExtRef, got nil")
+	}
+	if parser.pkg.PackageExternalReferences[0] == nil {
+		t.Errorf("expected non-nil PackageExternalReferences[0], got nil")
+	}
+	if parser.pkgExtRef != parser.pkg.PackageExternalReferences[0] {
+		t.Errorf("expected pkgExtRef to match PackageExternalReferences[0], got no match")
+	}
+	err = parser.parsePairFromPackage2_1("ExternalRefComment", ref1Comment)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	err = parser.parsePairFromPackage2_1("ExternalRef", ref2)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if len(parser.pkg.PackageExternalReferences) != 2 {
+		t.Errorf("expected 2 external references, got %d", len(parser.pkg.PackageExternalReferences))
+	}
+	if parser.pkgExtRef == nil {
+		t.Errorf("expected non-nil pkgExtRef, got nil")
+	}
+	if parser.pkg.PackageExternalReferences[1] == nil {
+		t.Errorf("expected non-nil PackageExternalReferences[1], got nil")
+	}
+	if parser.pkgExtRef != parser.pkg.PackageExternalReferences[1] {
+		t.Errorf("expected pkgExtRef to match PackageExternalReferences[1], got no match")
+	}
+	err = parser.parsePairFromPackage2_1("ExternalRefComment", ref2Comment)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	// finally, check these values
+	gotRef1 := parser.pkg.PackageExternalReferences[0]
+	if gotRef1.Category != ref1Category {
+		t.Errorf("expected ref1 category to be %s, got %s", gotRef1.Category, ref1Category)
+	}
+	if gotRef1.RefType != ref1Type {
+		t.Errorf("expected ref1 type to be %s, got %s", gotRef1.RefType, ref1Type)
+	}
+	if gotRef1.Locator != ref1Locator {
+		t.Errorf("expected ref1 locator to be %s, got %s", gotRef1.Locator, ref1Locator)
+	}
+	if gotRef1.ExternalRefComment != ref1Comment {
+		t.Errorf("expected ref1 comment to be %s, got %s", gotRef1.ExternalRefComment, ref1Comment)
+	}
+	gotRef2 := parser.pkg.PackageExternalReferences[1]
+	if gotRef2.Category != ref2Category {
+		t.Errorf("expected ref2 category to be %s, got %s", gotRef2.Category, ref2Category)
+	}
+	if gotRef2.RefType != ref2Type {
+		t.Errorf("expected ref2 type to be %s, got %s", gotRef2.RefType, ref2Type)
+	}
+	if gotRef2.Locator != ref2Locator {
+		t.Errorf("expected ref2 locator to be %s, got %s", gotRef2.Locator, ref2Locator)
+	}
+	if gotRef2.ExternalRefComment != ref2Comment {
+		t.Errorf("expected ref2 comment to be %s, got %s", gotRef2.ExternalRefComment, ref2Comment)
+	}
+
 }
 
 func TestParser2_1CanParsePackageSupplierPersonTag(t *testing.T) {
@@ -373,6 +591,51 @@
 
 }
 
+func TestPackageExternalRefPointerChangesAfterTags(t *testing.T) {
+	parser := tvParser2_1{
+		doc: &spdx.Document2_1{},
+		st:  psPackage2_1,
+		pkg: &spdx.Package2_1{PackageName: "p1", IsUnpackaged: false},
+	}
+	parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+	ref1 := "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+	err := parser.parsePairFromPackage2_1("ExternalRef", ref1)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkgExtRef == nil {
+		t.Errorf("expected non-nil external reference pointer, got nil")
+	}
+
+	// now, a comment; pointer should go away
+	err = parser.parsePairFromPackage2_1("ExternalRefComment", "whatever")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkgExtRef != nil {
+		t.Errorf("expected nil external reference pointer, got non-nil")
+	}
+
+	ref2 := "Other LocationRef-something https://example.com/whatever"
+	err = parser.parsePairFromPackage2_1("ExternalRef", ref2)
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkgExtRef == nil {
+		t.Errorf("expected non-nil external reference pointer, got nil")
+	}
+
+	// and some other random tag makes the pointer go away too
+	err = parser.parsePairFromPackage2_1("PackageSummary", "whatever")
+	if err != nil {
+		t.Errorf("expected nil error, got %v", err)
+	}
+	if parser.pkgExtRef != nil {
+		t.Errorf("expected nil external reference pointer, got non-nil")
+	}
+}
+
 // ===== Helper function tests =====
 
 func TestCanCheckAndExtractExcludesFilenameAndCode(t *testing.T) {
@@ -388,3 +651,45 @@
 		t.Errorf("got %v for gotFileName", gotFileName)
 	}
 }
+
+func TestCanExtractPackageExternalReference(t *testing.T) {
+	ref1 := "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+	category := "SECURITY"
+	refType := "cpe23Type"
+	location := "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+
+	gotCategory, gotRefType, gotLocation, err := extractPackageExternalReference(ref1)
+	if err != nil {
+		t.Errorf("got non-nil error: %v", err)
+	}
+	if gotCategory != category {
+		t.Errorf("expected category %s, got %s", category, gotCategory)
+	}
+	if gotRefType != refType {
+		t.Errorf("expected refType %s, got %s", refType, gotRefType)
+	}
+	if gotLocation != location {
+		t.Errorf("expected location %s, got %s", location, gotLocation)
+	}
+}
+
+func TestCanExtractPackageExternalReferenceWithExtraWhitespace(t *testing.T) {
+	ref1 := "  SECURITY    \t cpe23Type   cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:* \t "
+	category := "SECURITY"
+	refType := "cpe23Type"
+	location := "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+
+	gotCategory, gotRefType, gotLocation, err := extractPackageExternalReference(ref1)
+	if err != nil {
+		t.Errorf("got non-nil error: %v", err)
+	}
+	if gotCategory != category {
+		t.Errorf("expected category %s, got %s", category, gotCategory)
+	}
+	if gotRefType != refType {
+		t.Errorf("expected refType %s, got %s", refType, gotRefType)
+	}
+	if gotLocation != location {
+		t.Errorf("expected location %s, got %s", location, gotLocation)
+	}
+}
diff --git a/v0/tvloader/parser2_1/types.go b/v0/tvloader/parser2_1/types.go
index 21f2d7a..7f3ff39 100644
--- a/v0/tvloader/parser2_1/types.go
+++ b/v0/tvloader/parser2_1/types.go
@@ -14,13 +14,14 @@
 	st tvParserState2_1
 
 	// current SPDX item being filled in, if any
-	pkg      *spdx.Package2_1
-	file     *spdx.File2_1
-	snippet  *spdx.Snippet2_1
-	otherLic *spdx.OtherLicense2_1
-	rln      *spdx.Relationship2_1
-	ann      *spdx.Annotation2_1
-	rev      *spdx.Review2_1
+	pkg       *spdx.Package2_1
+	pkgExtRef *spdx.PackageExternalReference2_1
+	file      *spdx.File2_1
+	snippet   *spdx.Snippet2_1
+	otherLic  *spdx.OtherLicense2_1
+	rln       *spdx.Relationship2_1
+	ann       *spdx.Annotation2_1
+	rev       *spdx.Review2_1
 	// don't need creation info pointer b/c only one,
 	// and we can get to it via doc.CreationInfo
 }