1
+ /**
2
+ * Extracts the skills data from SFIA 9 Excel file and converts it
3
+ * into a JSON format expected by NIWA's Position Description Generator tool.
4
+ *
5
+ * Run this in the Code Editor and copy the Console.log output into a
6
+ * json_source.json file.
7
+ *
8
+ * Future SFIA versions may have different Excel formats. At minimum, check:
9
+ * 1. Worksheet name is "Skills"
10
+ * 2. Skills table with the important info is between columns I to V
11
+ * 3. Column headers exactly match SkillsTableData interface definition,
12
+ * including spaces and casing
13
+ * 4. Skills are already grouped by Category and Subcategory.
14
+ * If not, uncomment the optional grouping block.
15
+ */
16
+ function main ( workbook : ExcelScript . Workbook ) : void {
17
+ let worksheet = workbook . getWorksheet ( "Skills" ) ;
18
+
19
+ if ( worksheet . getTables ( ) . length === 0 )
20
+ {
21
+ // Create an Excel table for easier looping later
22
+ let newTable = workbook . addTable ( worksheet . getRange ( "A1:V148" ) , true ) ;
23
+
24
+ // Optional: Group table by Category and Subcategory if needed
25
+ // newTable.getSort().apply([
26
+ // { key: 11, ascending: true }, //column L
27
+ // { key: 12, ascending: true }], //column M
28
+ // false);
29
+ }
30
+
31
+ const table = worksheet . getTables ( ) [ 0 ] ;
32
+
33
+ // Get all the values from the table as text.
34
+ const texts = table . getRange ( ) . getTexts ( ) ;
35
+
36
+ // Create an array of JSON objects that match the row structure.
37
+ let tableRows : SkillsTableData [ ] = [ ] ;
38
+ if ( table . getRowCount ( ) > 0 ) {
39
+ tableRows = returnObjectFromValues ( texts ) ;
40
+ }
41
+
42
+ // Create the JSON object expected by NIWA's Position Description Generator tool
43
+ let displayStructure = mapToDisplayStructure ( tableRows ) ;
44
+
45
+ // Log the information for copying into the json_source.json file
46
+ console . log ( JSON . stringify ( displayStructure ) ) ;
47
+ return ;
48
+ }
49
+
50
+ // This function converts a 2D array of values into a SkillsTableData array.
51
+ function returnObjectFromValues ( values : string [ ] [ ] ) : SkillsTableData [ ] {
52
+ let objectArray : SkillsTableData [ ] = [ ] ;
53
+ let objectKeys : string [ ] = [ ] ;
54
+ for ( let i = 0 ; i < values . length ; i ++ ) {
55
+ if ( i === 0 ) {
56
+ objectKeys = values [ i ] ;
57
+ continue ;
58
+ }
59
+
60
+ let object = { } ;
61
+ for ( let j = 0 ; j < values [ i ] . length ; j ++ ) {
62
+ object [ objectKeys [ j ] ] = values [ i ] [ j ] ;
63
+ }
64
+
65
+ objectArray . push ( object as SkillsTableData ) ;
66
+ }
67
+
68
+ return objectArray ;
69
+ }
70
+
71
+ // This function converts SkillsTableData to the structure expected by NIWA
72
+ function mapToDisplayStructure ( table : SkillsTableData [ ] ) : object {
73
+ let result = { } ;
74
+
75
+ let categoryWatermark = "" ;
76
+ let subcategoryWatermark = "" ;
77
+
78
+ let category = { } ;
79
+ let subcategory = { } ;
80
+
81
+ let maxIndex = table . length - 1 ;
82
+
83
+ for ( let i = 0 ; i < table . length ; i ++ ) {
84
+ let row = table [ i ] ;
85
+
86
+ //build the Levels
87
+ let levels : Levels = {
88
+ 1 : getLevelDescription ( row [ "Level 1 description" ] ) ,
89
+ 2 : getLevelDescription ( row [ "Level 2 description" ] ) ,
90
+ 3 : getLevelDescription ( row [ "Level 3 description" ] ) ,
91
+ 4 : getLevelDescription ( row [ "Level 4 description" ] ) ,
92
+ 5 : getLevelDescription ( row [ "Level 5 description" ] ) ,
93
+ 6 : getLevelDescription ( row [ "Level 6 description" ] ) ,
94
+ 7 : getLevelDescription ( row [ "Level 7 description" ] )
95
+ } ;
96
+
97
+ // build the Skill
98
+ let skill : Skill = {
99
+ code : row . Code ,
100
+ description : row [ "Overall description" ] ,
101
+ levels : levels ,
102
+ url : row . URL
103
+ } ;
104
+
105
+ // update both watermarks
106
+ categoryWatermark = row . Category ;
107
+ subcategoryWatermark = row . Subcategory ;
108
+
109
+ // add to subcategory
110
+ subcategory [ row . Skill ] = skill ;
111
+
112
+ // peek ahead: if this is the last row or next subcategory is different,
113
+ // then add this subcategory to the category and re-initialise
114
+ if ( i == maxIndex || table [ i + 1 ] . Subcategory != subcategoryWatermark ) {
115
+ //console.log(JSON.stringify(subcategory));
116
+ category [ subcategoryWatermark ] = subcategory ;
117
+ subcategory = { } ;
118
+ }
119
+
120
+ // peek ahead: if this is the last row or next category is different,
121
+ // then add this category to the result and re-initialise
122
+ if ( i == maxIndex || table [ i + 1 ] . Category != categoryWatermark ) {
123
+ result [ categoryWatermark ] = category ;
124
+ category = { } ;
125
+ }
126
+ }
127
+
128
+ return result ;
129
+ }
130
+
131
+ // this function ensures that empty levels do not have a property defined
132
+ function getLevelDescription ( description : string ) : string | undefined {
133
+ return description && description . trim ( ) !== "" ? description : undefined ;
134
+ }
135
+
136
+ interface SkillsTableData {
137
+ Code : string ;
138
+ URL : string ;
139
+ Skill : string ;
140
+ Category : string ;
141
+ Subcategory : string ;
142
+ "Overall description" : string ;
143
+ "Guidance notes" : string ;
144
+ "Level 1 description" : string ;
145
+ "Level 2 description" : string ;
146
+ "Level 3 description" : string ;
147
+ "Level 4 description" : string ;
148
+ "Level 5 description" : string ;
149
+ "Level 6 description" : string ;
150
+ "Level 7 description" : string ;
151
+ }
152
+
153
+ interface Skill {
154
+ code : string ;
155
+ levels : Levels ;
156
+ description : string ;
157
+ url : string ;
158
+ }
159
+
160
+ interface Levels {
161
+ 1 ?: string ;
162
+ 2 ?: string ;
163
+ 3 ?: string ;
164
+ 4 ?: string ;
165
+ 5 ?: string ;
166
+ 6 ?: string ;
167
+ 7 ?: string ;
168
+ }
0 commit comments