data.go 11.6 KB
Newer Older
1
2
3
4
package data

import (
	"encoding/json"
5
6
	"encoding/xml"
	"io/ioutil"
7
8
)

9
10
const SCHEMA_VERSION = 1

11
// ResumeData is the outermost container for resume data.
12
type ResumeData struct {
13
14
	// XMLName provides a name for the top-level element, when working with resume data files in XML format.  This
	// field is ignored when working with files in JSON format.
15
	XMLName xml.Name `xml:"resume" json:"-"`
16
17
18
19
20
	// Version is an identifier for the schema structure.  If breaking changes occur in the future, then ResumeFodder
	// can use this value to recognize the incompatibility and provide options.
	Version int    `xml:"version" json:"version"`
	Basics  Basics `xml:"basics" json:"basics"`
	Work    []Work `xml:"work" json:"work"`
21
22
23
24
25
26
27
28
29
30
31
	// AdditionalWork is an extra field, not found within the standard JSON-Resume spec.  It is intended to store
	// employment history that should be presented differently from that in the main "Work" field.
	//
	// Specifically, if you have a lengthy work history, then you might store the oldest jobs in this field... so that
	// a template can present them in abbreviated format (i.e. no highlights), with perhaps a "Further details
	// available upon request"-type note.  It could similarly be used for high-school or college jobs that are
	// only worth mentioning for entry-level candidates only.
	//
	// Obviously, the records in this extra field would be ignored if you used your data file with a standard
	// JSON-Resume processor.  Otherwise, migration would require you to move any "AdditionalWork" records to the
	// "Work" field.
32
33
34
35
36
37
38
39
40
41
42
	AdditionalWork []Work `xml:"additionalWork" json:"additionalWork"`
	// WorkLabel is an extra field, not found within the standard JSON-Resume spec.  It is intended to tell templates
	// how to present the "Work" and "AdditionalWork" sections, when both are used (e.g. "Recent Experience"
	// versus "Prior Experience").
	WorkLabel string `xml:"workLabel" json:"workLabel"`
	// AdditionalWorkLabel is an extra field, not found within the standard JSON-Resume spec.  It is intended to tell
	// templates how to present the "Work" and "AdditionalWork" sections, when both are used (e.g. "Recent Experience"
	// versus "Prior Experience").
	AdditionalWorkLabel string        `xml:"additionalWorkLabel" json:"additionalWorkLabel"`
	Education           []Education   `xml:"education" json:"education"`
	Publications        []Publication `xml:"publications" json:"publications"`
43
44
45
46
47
48
49
50
51
52
	// AdditionalPublications is an extra field, not found within the standard JSON-Resume spec.  It is intended to
	// store publications that should be presented differently from those in the main "Publications" field.
	//
	// Specifically, if you collaborated on publications in which you were not an author or co-author (e.g. a technical
	// reviewer instead), then you might store those publications here so that a template can present them
	// without implying that you were an author.
	//
	// Obviously, the records in this extra field would be ignored if you used your data file with a standard
	// JSON-Resume processor.  Otherwise, migration would require you to move any "AdditionalPublications" records to
	// the "Publications" field.
53
	AdditionalPublications []Publication `xml:"additionalPublications" json:"additionalPublications"`
54
55
56
	// PublicationsLabel is an extra field, not found within the standard JSON-Resume spec.  It is intended to tell
	// templates how to present the "Publications" and "AdditionalPublications" sections, when both are used
	// (e.g. "Publications (Author)" versus "Publications (Technical Reviewer)").
57
	PublicationsLabel string `xml:"publicationsLabel" json:"publicationsLabel"`
58
59
60
	// AdditionalPublicationsLabel is an extra field, not found within the standard JSON-Resume spec.  It is intended
	// to tell templates how to present the "Publications" and "AdditionalPublications" sections, when both are used
	// (e.g. "Publications (Author)" versus "Publications (Technical Reviewer)").
61
62
	AdditionalPublicationsLabel string  `xml:"additionalPublicationsLabel" json:"additionalPublicationsLabel"`
	Skills                      []Skill `xml:"skills" json:"skills"`
63
64
}

65
66
// Basics is a container for top-level resume data.  These fields could just as well hang off the parent "ResumeData"
// struct, but this structure mirrors how the JSON-Resume spec arranges them.
67
type Basics struct {
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
	Name    string `xml:"name" json:"name"`
	Label   string `xml:"label" json:"label"`
	Picture string `xml:"picture" json:"picture"`
	Email   string `xml:"email" json:"email"`
	Phone   string `xml:"phone" json:"phone"`
	Degree  string `xml:"degree" json:"degree"`
	Website string `xml:"website" json:"website"`
	Summary string `xml:"summary" json:"summary"`
	// Highlights is an extra field, not found within the standard JSON-Resume spec.  It is intended for additional
	// top-level information, that a template might present with a bullet-point list or other similar formatting
	// next to the top-level "Summary" field.
	//
	// Obviously, the records in this extra field would be ignored if you used your data file with a standard
	// JSON-Resume processor.  Once the other JSON-Resume processors gain mature support for HTML and/or Markdown
	// line-break formatting within field values, then perhaps you could migrate "Highlights" data to within the
	// "Summary" field.
	Highlights []string        `xml:"highlights" json:"highlights"`
85
86
	Location   Location        `xml:"location" json:"location"`
	Profiles   []SocialProfile `xml:"profiles" json:"profiles"`
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
}

type Location struct {
	Address     string `xml:"address" json:"address"`
	PostalCode  string `xml:"postalCode" json:"postalCode"`
	City        string `xml:"city" json:"city"`
	CountryCode string `xml:"countryCode" json:"countryCode"`
	Region      string `xml:"region" json:"region"`
}

type SocialProfile struct {
	Network  string `xml:"network" json:"network"`
	Username string `xml:"username" json:"username"`
	Url      string `xml:"url" json:"url"`
}

type Work struct {
104
	// TODO: Perhaps job listings should have 'City' and 'Region' extension fields, as this is commonly found on resumes
105
106
107
	Company    string   `xml:"company" json:"company"`
	Position   string   `xml:"position" json:"position"`
	Website    string   `xml:"website" json:"website"`
108
109
	StartDate  string   `xml:"startDate" json:"startDate"`
	EndDate    string   `xml:"endDate" json:"endDate"`
110
	Summary    string   `xml:"summary" json:"summary"`
111
112
113
114
	Highlights []string `xml:"highlights" json:"highlights"`
}

type Education struct {
115
	// TODO: Perhaps education listings should have 'City' and 'Region' extension fields, as this is commonly found on resumes
116
117
118
	Institution string   `xml:"institution" json:"institution"`
	Area        string   `xml:"area" json:"area"`
	StudyType   string   `xml:"studyType" json:"studyType"`
119
120
	StartDate   string   `xml:"startDate" json:"startDate"`
	EndDate     string   `xml:"endDate" json:"endDate"`
121
	GPA         string   `xml:"gpa" json:"gpa"`
122
123
124
	Courses     []string `xml:"courses" json:"courses"`
}

125
126
127
128
129
type PublicationGroup struct {
	Name         string        `xml:"name" json:"name"`
	Publications []Publication `xml:"publications" json:"publications"`
}

130
131
132
type Publication struct {
	Name        string `xml:"name" json:"name"`
	Publisher   string `xml:"publisher" json:"publisher"`
133
	ReleaseDate string `xml:"releaseDate" json:"releaseDate"`
134
135
	Website     string `xml:"website" json:"website"`
	Summary     string `xml:"summary" json:"summary"`
136
137
138
139
	// ISBN is an extra field, not found within the standard JSON-Resume spec.  Obviously, this value will be
	// ignored if you used your data file with another JSON-Resume processor.  You could perhaps migrate by
	// cramming this info into the "Summary" field.
	ISBN string `xml:"isbn" json:"isbn"`
140
141
142
}

type Skill struct {
143
144
	Name     string   `xml:"name" json:"name"`
	Level    string   `xml:"level" json:"level"`
145
146
147
	Keywords []string `xml:"keywords" json:"keywords"`
}

148
149
// NewResumeData initializes a ResumeData struct, with ALL nested structs initialized
// to empty state (rather than just omitted).  Useful for generating a blank XML or JSON
150
151
152
// file with all fields forced to be present.
//
// Of course, if you simply need to initialize a blank struct without superfluous
153
// nested fields, then you can always instead simply declare:
154
155
156
//
// data := data.ResumeData{}
//
157
158
func NewResumeData() ResumeData {
	return ResumeData{
159
		Version: SCHEMA_VERSION,
160
		Basics: Basics{
161
			Location: Location{},
162
			Profiles: []SocialProfile{{}},
163
		},
164
		Work: []Work{
165
			{
166
				Highlights: []string{""},
167
168
			},
		},
169
		AdditionalWork: []Work{
170
			{
171
				Highlights: []string{""},
172
173
			},
		},
174
		Education: []Education{
175
			{
176
				Courses: []string{""},
177
178
			},
		},
179
180
		Publications:           []Publication{{}},
		AdditionalPublications: []Publication{{}},
181
		Skills: []Skill{
182
183
			{
				Keywords: []string{""},
184
185
186
187
188
			},
		},
	}
}

189
// FromXmlString loads a ResumeData struct from a string of XML text.
190
191
192
193
func FromXmlString(xmlString string) (ResumeData, error) {
	return fromXml([]byte(xmlString))
}

194
// FromXmlFile loads a ResumeData struct from an XML file.
195
196
197
198
199
200
201
202
func FromXmlFile(xmlFilename string) (ResumeData, error) {
	bytes, err := ioutil.ReadFile(xmlFilename)
	if err != nil {
		return ResumeData{}, err
	}
	return fromXml(bytes)
}

203
// fromXml is a private function that provides the core logic for `FromXmlString` and `FromXmlFile`.
204
205
206
func fromXml(xmlBytes []byte) (ResumeData, error) {
	var data ResumeData
	err := xml.Unmarshal(xmlBytes, &data)
207
208
	if err == nil {
		// The marshal process in `toXml()` will use field tags to populate the `ResumeData.XMLName` field
209
		// with `resume`.  When unmarshalling from XML, we likewise strip this field value back off... to
210
211
212
		// better facilitate equality comparison between `ResumeData` structs (e.g. in unit testing).
		data.XMLName.Local = ""
	}
213
214
215
	return data, err
}

216
// ToXmlString writes a ResumeData struct to a string of XML text.
217
func ToXmlString(data ResumeData) (string, error) {
218
219
220
221
222
	xmlBytes, err := toXml(data)
	if err != nil {
		return "", err
	}
	return string(xmlBytes[:]), nil
223
224
}

225
// ToXmlFile writes a ResumeData struct to an XML file.
226
func ToXmlFile(data ResumeData, xmlFilename string) error {
227
	xmlBytes, err := toXml(data)
228
	if err != nil {
229
230
231
232
		return err
	}
	if err := ioutil.WriteFile(xmlFilename, xmlBytes, 0644); err != nil {
		return err
233
	}
234
235
236
	return nil
}

237
// toXml is a private function that provides the core logic for `ToXmlString` and `ToXmlFile`.
238
239
func toXml(data ResumeData) ([]byte, error) {
	return xml.MarshalIndent(data, "", "  ")
240
241
}

242
// FromJsonString loads a ResumeData struct from a string of JSON text.
243
244
245
246
func FromJsonString(jsonString string) (ResumeData, error) {
	return fromJson([]byte(jsonString))
}

247
// FromJsonFile loads a ResumeData struct from a JSON file.
248
249
250
251
252
253
254
255
func FromJsonFile(jsonFilename string) (ResumeData, error) {
	bytes, err := ioutil.ReadFile(jsonFilename)
	if err != nil {
		return ResumeData{}, err
	}
	return fromJson(bytes)
}

256
// fromJson is a private function that provides the core logic for `FromJsonString` and `FromJsonFile`.
257
258
259
260
261
262
func fromJson(jsonBytes []byte) (ResumeData, error) {
	var data ResumeData
	err := json.Unmarshal(jsonBytes, &data)
	return data, err
}

263
// ToJsonString writes a ResumeData struct to a string of JSON text.
264
func ToJsonString(data ResumeData) (string, error) {
265
266
267
268
269
	jsonBytes, err := toJson(data)
	if err != nil {
		return "", err
	}
	return string(jsonBytes[:]), nil
270
271
}

272
// ToJsonFile writes a ResumeData struct to a JSON file.
273
func ToJsonFile(data ResumeData, jsonFilename string) error {
274
	jsonBytes, err := toJson(data)
275
	if err != nil {
276
277
278
279
		return err
	}
	if err := ioutil.WriteFile(jsonFilename, jsonBytes, 0644); err != nil {
		return err
280
	}
281
282
283
	return nil
}

284
// toJson is a private function that provides the core logic for `ToJsonString` and `ToJsonFile`.
285
286
func toJson(data ResumeData) ([]byte, error) {
	return json.MarshalIndent(data, "", "  ")
287
}