├── .gitignore ├── start_static_server ├── package.json ├── validation ├── FieldShape.ttl ├── ActivityShape.ttl └── ActivitySetShape.ttl ├── README.md └── tools ├── mindloggerConverter.py ├── reproSchema2RedCap.js ├── ABCDredcap2schema.js ├── create_neurovault_schema.py ├── maltreatmentRedCap2schema.js ├── HBN2schema.js ├── RedCap2ReproSchema.js └── jsonld-expander.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | activities/.DS_Store 4 | node_modules 5 | local_data 6 | -------------------------------------------------------------------------------- /start_static_server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import http.server 3 | import os 4 | 5 | class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): 6 | def end_headers(self): 7 | self.send_header("Access-Control-Allow-Origin", "*") 8 | http.server.SimpleHTTPRequestHandler.end_headers(self) 9 | 10 | if __name__ == '__main__': 11 | http.server.test(HandlerClass=MyHTTPRequestHandler, port=8000) 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schema-standards", 3 | "version": "1.0.0", 4 | "description": "Schema Standardization", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/sanuann/schema-standardization.git" 15 | }, 16 | "author": "Sanu Ann Abraham ", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/sanuann/schema-standardization/issues" 20 | }, 21 | "homepage": "https://github.com/sanuann/schema-standardization#readme", 22 | "dependencies": { 23 | "camelcase": "^5.0.0", 24 | "csv-writer": "^1.6.0", 25 | "fast-csv": "^4.3.6", 26 | "is-html": "^1.1.0", 27 | "json-2-csv": "^3.6.2", 28 | "jsonld": "^3.0.1", 29 | "lodash": "^4.17.19", 30 | "mkdirp": "^0.5.1", 31 | "node-html-parser": "^1.1.11", 32 | "shelljs": "^0.8.3", 33 | "striptags": "^3.1.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /validation/FieldShape.ttl: -------------------------------------------------------------------------------- 1 | schema:ActivityShape 2 | a sh:NodeShape ; 3 | sh:targetClass schema:Activity ; 4 | sh:property [ 5 | sh:path schema:description ; 6 | sh:datatype xsd:string ; 7 | sh:minCount 1 ; 8 | sh:maxCount 1 ; 9 | ] ; 10 | sh:property [ 11 | sh:path schema:schemaVersion; 12 | sh:node schema:SchemaVersionShape ; 13 | sh:minCount 1 ; 14 | sh:maxCount 1 ; 15 | ] ; 16 | sh:property [ 17 | sh:path schema:version ; 18 | sh:node schema:VersionShape ; 19 | sh:minCount 1 ; 20 | sh:maxCount 1 ; 21 | ] ; 22 | sh:property [ 23 | sh:path skos:prefLabel ; 24 | sh:datatype xsd:string ; 25 | sh:minCount 1 ; 26 | sh:maxCount 1 ; 27 | ] ; 28 | sh:property [ 29 | sh:path skos:altLabel ; 30 | sh:datatype xsd:string ; 31 | sh:minCount 1 ; 32 | sh:maxCount 1 ; 33 | ] ; 34 | sh:property [ 35 | sh:path repronim:question; 36 | sh:datatype xsd:string ; 37 | sh:minCount 1 ; 38 | sh:maxCount 1 ; 39 | ] ; 40 | sh:property [ 41 | sh:path repronim:ui ; 42 | sh:minCount 1 ; 43 | sh:maxCount 1 ; 44 | sh:node repronim:UiShape ; 45 | ] . 46 | 47 | repronim:UiShape 48 | a sh:NodeShape ; 49 | sh:property [ 50 | sh:path repronim:inputType ; 51 | sh:minCount 1 ; 52 | sh:maxCount 1 ; 53 | sh:datatype xsd:string ; 54 | ] . 55 | 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reproschema-builder 2 | 3 | Create protocol from a CSV table: 4 | 5 | 1. Convert any survey into the format of the data dictionary template below: 6 | 7 | 8 | ### template for CSV 9 | 10 | Variable / Field Name | Section Header | Form Name | Field Type | multipleChoice | Field Label | Choices, Calculations, OR Slider Labels | minVal | maxVal | Branching Logic (Show field only if...) | 11 | |------------| ------------| ------------| ------------| ------------| ------------| ------------| ------------| ------------| ------------| 12 | | | | | | | | | | | | 13 | 14 | + **Variable / Field Name:** The id for the survey item (required).
15 | + **Section Header:** Preamble for the survey item (leave blank if you don't want a preamble).
16 | + **Form Name:** Name of the activity the survey item is a part of (required).
17 | + **Field Type:** Type of reponse for survey item, currently supports: 18 | + `radio` 19 | + `select` 20 | + `slider` 21 | + `text` 22 | + `static` 23 | + `timeRange` 24 |
25 | + **multipleChoice:** Used for `radio` items. Set value=1 if the item is a multi-choice checklist, leave blank if single choice.
26 | + **Field Label:** Question/text for survey item.
27 | + **Choices, Calculations, OR Slider Labels:** Reponse list for survey item, in the form of `value1, choice1 | value2, choice2 | value 3, choice3` etc. Leave blank for `text`, `static`, and `timeRange` items.
28 | + **maxVal** Used for slider items, the text to display for max value of slider bar. Leave blank for other items types.
29 | + **minVal** Used for slider items, the text to display for min value of slider bar. Leave blank for other items types.
30 | + **Branching Logic (Show field only if...):** For conditional logic. For example, if `question2` only shows when `question1` has value 1, fill this column with `[question1]=1` for the row of `question2`. If the question has no conditional logic, leave this column blank.
31 | 32 | 33 | ### Usage: 34 | `node tools/RedCap2ReproSchema.js path_to_your_csv_file` 35 | 36 | -------------------------------------------------------------------------------- /validation/ActivityShape.ttl: -------------------------------------------------------------------------------- 1 | schema:ActivityShape 2 | a sh:NodeShape ; 3 | sh:targetClass schema:Activity ; 4 | sh:property [ 5 | sh:path schema:description ; 6 | sh:datatype xsd:string ; 7 | sh:minCount 1 ; 8 | sh:maxCount 1 ; 9 | ] ; 10 | sh:property [ 11 | sh:path schema:schemaVersion; 12 | sh:node schema:SchemaVersionShape ; 13 | sh:minCount 1 ; 14 | sh:maxCount 1 ; 15 | ] ; 16 | sh:property [ 17 | sh:path schema:version ; 18 | sh:node schema:VersionShape ; 19 | sh:minCount 1 ; 20 | sh:maxCount 1 ; 21 | ] ; 22 | sh:property [ 23 | sh:path skos:prefLabel ; 24 | sh:datatype xsd:string ; 25 | sh:minCount 1 ; 26 | sh:maxCount 1 ; 27 | ] ; 28 | sh:property [ 29 | sh:path skos:altLabel ; 30 | sh:datatype xsd:string ; 31 | sh:minCount 1 ; 32 | sh:maxCount 1 ; 33 | ] ; 34 | sh:property [ 35 | sh:path repronim:preamble; 36 | sh:datatype xsd:string ; 37 | sh:maxCount 1 ; 38 | ] ; 39 | sh:property [ 40 | sh:path repronim:scoringLogic; 41 | sh:datatype xsd:string ; 42 | sh:maxCount 1 ; 43 | sh:node repronim:ScoringLogicShape ; 44 | ] ; 45 | sh:property [ 46 | sh:path repronim:branchLogic; 47 | sh:maxCount 1 ; 48 | sh:node repronim:BranchLogicShape ; 49 | ] ; 50 | sh:property [ 51 | sh:path repronim:variableMap; 52 | sh:datatype xsd:string ; 53 | sh:minCount 1 ; 54 | sh:maxCount 1 ; 55 | sh:node repronim:VariableMapShape ; 56 | ] ; 57 | sh:property [ 58 | sh:path repronim:ui ; 59 | sh:minCount 1 ; 60 | sh:maxCount 1 ; 61 | sh:node repronim:UiShape ; 62 | ] . 63 | 64 | repronim:UiShape 65 | a sh:NodeShape ; 66 | sh:property [ 67 | sh:path repronim:order ; 68 | sh:minCount 1 ; 69 | sh:maxCount 1 ; 70 | sh:node repronim:OrderShape ; 71 | ] ; 72 | sh:property [ 73 | sh:path repronim:shuffle ; 74 | sh:datatype schema:boolean ; 75 | sh:maxCount 1 ; 76 | ] ; 77 | sh:property [ 78 | sh:path repronim:visibility ; 79 | sh:minCount 1 ; 80 | sh:maxCount 1 ; 81 | sh:node repronim:VisibilityShape ; 82 | ] . 83 | 84 | repronim:ScoringLogicShape 85 | a sh:NodeShape ; 86 | sh:property [ 87 | 88 | ] . 89 | 90 | repronim:BranchLogicShape 91 | a sh:NodeShape ; 92 | sh:property [ 93 | 94 | ] . 95 | 96 | repronim:VariableMapShape 97 | a sh:NodeShape ; 98 | sh:property [ 99 | 100 | ] . 101 | 102 | -------------------------------------------------------------------------------- /validation/ActivitySetShape.ttl: -------------------------------------------------------------------------------- 1 | schema:ActivityShape 2 | a sh:NodeShape ; 3 | sh:targetClass schema:Activity ; 4 | sh:property [ 5 | sh:path schema:description ; 6 | sh:datatype xsd:string ; 7 | sh:minCount 1 ; 8 | sh:maxCount 1 ; 9 | ] ; 10 | sh:property [ 11 | sh:path schema:schemaVersion; 12 | sh:node schema:SchemaVersionShape ; 13 | sh:minCount 1 ; 14 | sh:maxCount 1 ; 15 | ] ; 16 | sh:property [ 17 | sh:path schema:version ; 18 | sh:node schema:VersionShape ; 19 | sh:minCount 1 ; 20 | sh:maxCount 1 ; 21 | ] ; 22 | sh:property [ 23 | sh:path skos:prefLabel ; 24 | sh:datatype xsd:string ; 25 | sh:minCount 1 ; 26 | sh:maxCount 1 ; 27 | ] ; 28 | sh:property [ 29 | sh:path skos:altLabel ; 30 | sh:datatype xsd:string ; 31 | sh:minCount 1 ; 32 | sh:maxCount 1 ; 33 | ] ; 34 | sh:property [ 35 | sh:path repronim:preamble; 36 | sh:datatype xsd:string ; 37 | sh:maxCount 1 ; 38 | ] ; 39 | sh:property [ 40 | sh:path repronim:scoringLogic; 41 | sh:datatype xsd:string ; 42 | sh:maxCount 1 ; 43 | sh:node repronim:ScoringLogicShape ; 44 | ] ; 45 | sh:property [ 46 | sh:path repronim:branchLogic; 47 | sh:maxCount 1 ; 48 | sh:node repronim:BranchLogicShape ; 49 | ] ; 50 | sh:property [ 51 | sh:path repronim:variableMap; 52 | sh:datatype xsd:string ; 53 | sh:minCount 1 ; 54 | sh:maxCount 1 ; 55 | sh:node repronim:VariableMapShape ; 56 | ] ; 57 | sh:property [ 58 | sh:path repronim:ui ; 59 | sh:minCount 1 ; 60 | sh:maxCount 1 ; 61 | sh:node repronim:UiShape ; 62 | ] . 63 | 64 | repronim:UiShape 65 | a sh:NodeShape ; 66 | sh:property [ 67 | sh:path repronim:order ; 68 | sh:minCount 1 ; 69 | sh:maxCount 1 ; 70 | sh:node repronim:OrderShape ; 71 | ] ; 72 | sh:property [ 73 | sh:path repronim:shuffle ; 74 | sh:datatype schema:boolean ; 75 | sh:maxCount 1 ; 76 | ] ; 77 | sh:property [ 78 | sh:path repronim:activity_display_name ; 79 | sh:minCount 1 ; 80 | sh:maxCount 1 ; 81 | sh:node repronim:ActivityDisplayNameShape ; 82 | ] . 83 | sh:property [ 84 | sh:path repronim:visibility ; 85 | sh:minCount 1 ; 86 | sh:maxCount 1 ; 87 | sh:node repronim:VisibilityShape ; 88 | ] . 89 | 90 | repronim:ScoringLogicShape 91 | a sh:NodeShape ; 92 | sh:property [ 93 | 94 | ] . 95 | 96 | repronim:BranchLogicShape 97 | a sh:NodeShape ; 98 | sh:property [ 99 | 100 | ] . 101 | 102 | repronim:VariableMapShape 103 | a sh:NodeShape ; 104 | sh:property [ 105 | 106 | ] . 107 | 108 | -------------------------------------------------------------------------------- /tools/mindloggerConverter.py: -------------------------------------------------------------------------------- 1 | from nested_lookup import nested_lookup 2 | import requests 3 | import pprint 4 | import json 5 | 6 | pp = pprint.PrettyPrinter(indent=4) 7 | 8 | # map mindlogger db schema keys to schema-standards 9 | schemaMap = { 10 | "_id": "@id", 11 | "_modelType": "@type", 12 | "description": "schema:description", 13 | "meta.abbreviation": "skos:altLabel", 14 | "meta.description": "skos:prefLabel" 15 | } 16 | 17 | mindloggerapiUrl = 'https://api.mindlogger.org/api/v1' 18 | urlEMAPhysicalHealth = mindloggerapiUrl+ '/folder/5bd88558336da80de9145b76' 19 | 20 | form_schema = requests.get(urlEMAPhysicalHealth).json() 21 | meta_form_info = nested_lookup(key='meta', document=form_schema) 22 | form_dict = { 23 | "@context": [ 24 | "https://raw.githubusercontent.com/ReproNim/schema-standardization/master/contexts/generic.jsonld" 25 | ], 26 | "schema:schemaVersion": "0.0.1", 27 | "schema:version": "0.0.1" 28 | } 29 | ui_list = ['screens', 'notification', 'permission', 'resumeMode'] 30 | 31 | for key, value in form_schema.items(): 32 | if key in schemaMap: 33 | mapped_key = schemaMap[key] 34 | if (mapped_key == '@type') and (value == 'folder'): 35 | value = "https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Activity.jsonld" 36 | form_dict[mapped_key] = value 37 | 38 | 39 | def get_item_ids(ui_item): 40 | value_list = [] 41 | for x in ui_item: 42 | value_list.append(x['@id']) 43 | return value_list 44 | 45 | 46 | for key, value in meta_form_info[0].items(): 47 | if ('meta.' + key) in schemaMap: 48 | mapped_key = schemaMap['meta.' + key] 49 | elif key in schemaMap: 50 | mapped_key = schemaMap[key] 51 | elif key in ui_list: 52 | mapped_key = 'ui' 53 | if key == 'screens': 54 | value = get_item_ids(value) 55 | key = 'order' 56 | value = {key: value} 57 | else: 58 | mapped_key = key 59 | if mapped_key in form_dict: 60 | form_dict[mapped_key].update(value) 61 | else: 62 | form_dict[mapped_key] = value 63 | 64 | form_name = form_dict['skos:altLabel'] 65 | form_name = ''.join(x for x in form_name.title() if not x.isspace()) 66 | print (form_name) 67 | with open(form_name + '.jsonld', 'w') as fp: 68 | json.dump(form_dict, fp, indent=4) 69 | 70 | itemMap = { 71 | "_id": "@id", 72 | "description": "schema:description", 73 | "text": "question" 74 | } 75 | item_ui = { 76 | 'skipToScreen': 'skipTo', 77 | 'skippable': 'requiredValue', 78 | 'surveyType': 'inputType' 79 | } 80 | resp_items = { 81 | 'mode': 'multipleChoice', 82 | 'options': 'choices', 83 | 'optionsCount': 'count', 84 | 'optionsMax': 'maxValue', 85 | 'optionsMin': 'minValue' 86 | } 87 | 88 | 89 | for item in form_schema['meta']['screens']: 90 | itemUrl = mindloggerapiUrl + '/' + item['@id'] 91 | item_schema = requests.get(itemUrl).json() # each item schema 92 | item_dict = { 93 | '@context': [ 94 | "https://raw.githubusercontent.com/ReproNim/schema-standardization/master/contexts/generic.jsonld"], 95 | '@type': "https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld", 96 | "schema:schemaVersion": "0.0.1", 97 | "schema:version": "0.0.1", 98 | } 99 | for key, value in item_schema.items(): 100 | if key in itemMap: 101 | mapped_key = itemMap[key] 102 | val = value 103 | item_dict[mapped_key] = value 104 | elif key == 'meta': 105 | for k, v in value.items(): 106 | val = v 107 | if k in itemMap: 108 | mapped_key = itemMap[k] 109 | val = v 110 | elif k in item_ui.keys(): 111 | mapped_key = 'ui' 112 | if k == 'surveyType' and v == 'list': 113 | v = 'radio' 114 | val = {item_ui[k]: v} 115 | elif k == 'survey': 116 | mapped_key = 'responseOptions' 117 | resp = {} 118 | for ky, vl in v.items(): 119 | if ky in resp_items.keys(): 120 | rk = resp_items[ky] 121 | if ky == 'mode': # MCQ 122 | if vl == 'single': 123 | vl = 'false' # one answer question 124 | else: 125 | vl = 'true' # multiple choice options 126 | if ky == 'options': 127 | new_vl = [] 128 | for c in vl: 129 | new_vl.append({'schema:name': c['text']}) 130 | vl = new_vl 131 | resp.update({rk: vl}) 132 | val = resp 133 | else: 134 | mapped_key = k 135 | val = v 136 | if mapped_key in item_dict: 137 | item_dict[mapped_key].update(val) 138 | else: 139 | item_dict[mapped_key] = val 140 | 141 | with open(item_schema['_id'] + '_schema.jsonld', 'w') as fp: 142 | json.dump(item_dict, fp, indent=4) 143 | -------------------------------------------------------------------------------- /tools/reproSchema2RedCap.js: -------------------------------------------------------------------------------- 1 | const schemaMap = { 2 | "Identifier?": "@id", 3 | "Variable / Field Name": "skos:altLabel", 4 | "Item Display Name": "skos:prefLabel", 5 | "Field Note": "schema:description", 6 | "Section Header": "preamble", // todo: check this 7 | "Field Label": "question",//description? 8 | "Field Type": "inputType", 9 | "Allow": "allow", 10 | "Required Field?": "requiredValue", 11 | "minVal": "schema:minValue", 12 | "maxVal": "schema:maxValue", 13 | "Choices, Calculations, OR Slider Labels": "choices", 14 | "Branching Logic (Show field only if...)": "visibility", 15 | "multipleChoice": "multipleChoice", 16 | "responseType": "@type" 17 | 18 | }; 19 | 20 | const fs = require('fs'); 21 | const _ = require('lodash'); 22 | const converter = require('json-2-csv'); 23 | const createCsvWriter = require('csv-writer').createObjectCsvWriter; 24 | const csvWriter = createCsvWriter({ 25 | path: './local_data/protocol_name.csv', 26 | header: [ 27 | {id: 'var_name', title: 'Variable / Field Name'}, 28 | {id: 'activity', title: 'Form Name'}, 29 | {id: 'section', title: 'Section Header'}, 30 | {id: 'field_type', title: 'Field Type'}, 31 | {id: 'field_label', title: 'Field Label'}, 32 | {id: 'choices', title: 'Choices, Calculations, OR Slider Labels'}, 33 | {id: 'field_notes', title: 'Field Note'}, 34 | {id: 'val_type_OR_slider', title: 'Text Validation Type OR Show Slider Number'}, 35 | {id: 'val_min', title: 'Text Validation Min'}, 36 | {id: 'val_max', title: 'Text Validation Max'}, 37 | {id: 'identifier', title: 'Form Name'}, 38 | {id: 'visibility', title: 'Branching Logic (Show field only if...)'}, 39 | {id: 'required', title: 'Required Field?'}, 40 | 41 | ] 42 | }); 43 | let csvData = []; 44 | var files = fs.readdirSync('./protocols/protocol_name'); 45 | // console.log(23, files); 46 | files.forEach(function (file) { 47 | let p; 48 | if (file.endsWith('_schema')) { 49 | parsedJSON = JSON.parse(fs.readFileSync('./protocols/protocol_name' + "/" + file, 'utf8')); 50 | 51 | } 52 | }); 53 | const activityList = parsedJSON.ui.order; 54 | csvData = []; 55 | activityList.forEach(function (activity) { 56 | // console.log(43, activity); 57 | parsedJSONActivity = JSON.parse(fs.readFileSync(`./activities/${activity}/${activity}_schema`, 'utf8')); 58 | (parsedJSONActivity.ui.order).forEach(function (item) { 59 | itemJSON = JSON.parse(fs.readFileSync(`./activities/${activity}/items/${item}`, 'utf8')); 60 | let rowData = {}; 61 | if (!_.isEmpty((parsedJSONActivity.scoringLogic))) { // refactor acc to new schema 62 | const matched_ScoreObj = _.filter(parsedJSONActivity.scoringLogic, scoreObj => scoreObj.variableName === itemJSON['@id']); 63 | if (!_.isEmpty(matched_ScoreObj)) { 64 | rowData.choices = matched_ScoreObj[0].jsExpression; 65 | // console.log(63, matched_ScoreObj[0].jsExpression); 66 | } 67 | } 68 | rowData = find_Ftype_and_colH(itemJSON, rowData); 69 | 70 | // get column I, J entries 71 | // get_colI_colJ_entries(itemJSON, rowData); 72 | if (itemJSON.responseOptions["schema:minValue"]) { 73 | rowData.val_min = itemJSON.responseOptions["schema:minValue"]; 74 | } 75 | if (itemJSON.responseOptions["schema:maxValue"]) { 76 | rowData.val_max = itemJSON.responseOptions["schema:maxValue"]; 77 | } 78 | 79 | if (itemJSON.responseOptions.choices) { 80 | let itemChoices = ''; 81 | (itemJSON.responseOptions.choices).forEach(function (ch) { 82 | // console.log(62, itemJSON['@id'], ch['schema:value'], ch['schema:name']); 83 | if (itemChoices) { 84 | itemChoices = itemChoices.concat(' | ', ch['schema:value'], ', ', ch['schema:name']); 85 | // console.log(66, itemChoices); 86 | } 87 | else itemChoices = itemChoices.concat(ch['schema:value'], ', ', ch['schema:name']); 88 | }); 89 | // console.log(69, itemChoices); 90 | rowData.choices = itemChoices; 91 | } 92 | 93 | if (itemJSON.responseOptions.requiredValue) { 94 | rowData.required = itemJSON.responseOptions.requiredValue; 95 | } 96 | 97 | if (itemJSON['skos:altLabel']) { 98 | rowData.field_notes = itemJSON['skos:altLabel']; 99 | } 100 | 101 | rowData.var_name = itemJSON['@id']; 102 | rowData.activity = activity; 103 | rowData.field_label = itemJSON.question.en; // for now returns only the english 104 | csvData.push(rowData); 105 | // csvData.push({ 106 | // var_name: itemJSON['@id'], 107 | // activity: activity, 108 | // field_type: f_type, 109 | // field_label: itemJSON.question.en, // for now returns only the english 110 | // required: itemJSON.responseOptions.requiredValue, 111 | // choices: itemChoices, 112 | // field_notes: itemJSON['skos:altLabel'], // need to check with satra, should also use language tag 113 | // val_type_OR_slider: col_h, 114 | // val_min: minVal, 115 | // val_max: fgg 116 | // }); 117 | }); 118 | }); 119 | 120 | function find_Ftype_and_colH(itemJSON, rowData) { 121 | let f_type = itemJSON.ui.inputType; 122 | let col_h = ''; 123 | if (itemJSON.ui.inputType === 'integer') { 124 | f_type = 'text'; 125 | col_h = 'number'; 126 | 127 | } else if (itemJSON.ui.inputType === 'select') { 128 | f_type = 'dropdown'; // what about if any thing in col H? 129 | } else if (itemJSON.ui.inputType === 'date') { 130 | f_type = 'text'; 131 | col_h = 'ddate_mdy'; 132 | 133 | } 134 | rowData.field_type = f_type; 135 | if (col_h) { 136 | // console.log(119, itemJSON['@id'], col_h); 137 | rowData.val_type_OR_slider = col_h; 138 | } 139 | return rowData; 140 | } 141 | 142 | 143 | // console.log(57, csvData); 144 | csvWriter 145 | .writeRecords(csvData) 146 | .then(()=> console.log('The CSV file was written successfully')); 147 | 148 | 149 | // converter.json2csvAsync(parsedJSONActivity.ui.order) 150 | // .then((csv) => { 151 | // console.log(57, csv); 152 | // }) 153 | // .catch((err) => { 154 | // console.log(60, err); 155 | // }); 156 | 157 | -------------------------------------------------------------------------------- /tools/ABCDredcap2schema.js: -------------------------------------------------------------------------------- 1 | /* ************ Constants **************************************************** */ 2 | const csv = require('fast-csv'); 3 | const fs = require('fs'); 4 | const camelcase = require('camelcase'); 5 | const mkdirp = require('mkdirp'); 6 | const HTMLParser = require ('node-html-parser'); 7 | 8 | const schemaMap = { 9 | "Identifier?": "@id", 10 | "Variable / Field Name": "skos:altLabel", 11 | "Field Note": "schema:description", 12 | "Section Header": "preamble", 13 | "Field Label": "question", 14 | "Field Type": "inputType", 15 | "Required Field?": "requiredValue", 16 | "Text Validation Min": "minValue", 17 | "Text Validation Max": "maxValue", 18 | "Choices, Calculations, OR Slider Labels": "choices", 19 | "Branching Logic (Show field only if...)": "branchLogic" 20 | } 21 | const uiList = ['inputType', 'shuffle']; 22 | const responseList = ['type', 'minValue', 'maxValue', 'requiredValue', 'multipleChoice']; 23 | const defaultLanguage = 'en'; 24 | /* **************************************************************************************** */ 25 | 26 | // Make sure we got a filename on the command line. 27 | if (process.argv.length < 3) { 28 | console.log('Usage: node ' + process.argv[1] + ' FILENAME'); 29 | process.exit(1); 30 | } 31 | // Read the file. 32 | let csvPath = process.argv[2]; 33 | let readStream = fs.createReadStream(csvPath).setEncoding('utf-8'); 34 | 35 | let schemaContextUrl = 'https://raw.githubusercontent.com/ReproNim/schema-standardization/master/contexts/generic.jsonld'; 36 | let formContextUrl = ''; 37 | let ins_name = ''; 38 | let order = []; 39 | let blObj = []; 40 | let graphArr = []; 41 | let formContext = {}; 42 | let itemOBj = { "@version": 1.1 }; 43 | let languages = []; 44 | let dataArr = []; 45 | 46 | // create directory structure - activities/form_name/items 47 | mkdirp('activities/demographics_and_background_information_v1/items', function (err) { 48 | if (err){ 49 | console.log(err); 50 | }else{ 51 | console.log('directory created in activities') 52 | } 53 | }); 54 | 55 | let options = { 56 | delimiter: ',', 57 | headers: true, 58 | objectMode: true, 59 | quote: '"', 60 | escape: '"', 61 | ignoreEmpty: true 62 | }; 63 | 64 | let readFileStream = fs.createReadStream(csvPath).setEncoding('utf-8'); 65 | 66 | readStream.pipe(csv(options)) 67 | .on('data', function(data){ 68 | dataArr.push(data); // Add a row 69 | ins_name = data['Form Name']; 70 | let field_name = data['Variable / Field Name']; 71 | // define item_x urls to be inserted in context 72 | itemOBj[field_name] = { "@id": `${ins_name}:${field_name}.jsonld` , "@type": "@id" }; 73 | itemOBj[ins_name] = `https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/${ins_name}/items/`; 74 | readFileStream.destroy(); 75 | }) 76 | .on('end', function(){ 77 | formContext['@context'] = itemOBj; 78 | const fc = JSON.stringify(formContext, null, 4); 79 | fs.writeFile('activities/family_history_assessment_parent/' + ins_name + '_context' + '.jsonld', fc, function(err) { 80 | console.log("Context created"); 81 | }); 82 | formContextUrl = `https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/${ins_name}/${ins_name}_context.jsonld`; 83 | for (let i = 0, len = dataArr.length; i < len; i++) { 84 | if (i === 0) { // take instrument name from first row - 'Form Name' column 85 | if (ins_name === '') { 86 | ins_name = dataArr[i]['Form Name']; 87 | } 88 | } 89 | if(languages.length === 0){ 90 | languages = parseLanguageIsoCodes(dataArr[i]['Field Label']); 91 | } 92 | 93 | processRow(dataArr[i]); 94 | } 95 | finishSchemaCreation(); 96 | }); 97 | 98 | function processRow(data){ 99 | let rowData = {}; 100 | let ui = {}; 101 | let rspObj = {}; 102 | let choiceList = []; 103 | rowData['@context'] = [schemaContextUrl, formContextUrl]; 104 | rowData['@type'] = 'https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld'; 105 | Object.keys(data).forEach(current_key => { 106 | if (current_key !== 'Form Name') { 107 | // get schema key from mapping.json corresponding to current_key 108 | if (schemaMap.hasOwnProperty(current_key)) { 109 | // check all ui elements to be nested under 'ui' key 110 | if (uiList.indexOf(schemaMap[current_key]) > -1) { 111 | 112 | if (rowData.hasOwnProperty('ui')) { 113 | rowData.ui[schemaMap[current_key]] = data[current_key]; 114 | } 115 | else { 116 | ui[schemaMap[current_key]] = data[current_key]; 117 | rowData['ui'] = ui; 118 | } 119 | } 120 | // parse choice field 121 | else if (schemaMap[current_key] === 'choices' & data[current_key] !== '') { 122 | 123 | // split string wrt '|' to get each choice 124 | let c = data[current_key].split('|'); 125 | // split each choice wrt ',' to get schema:name and schema:value 126 | c.forEach(ch => { 127 | let choiceObj = {}; 128 | let cs = ch.split(', '); 129 | // create name and value pair for each choice option 130 | choiceObj['schema:value'] = parseInt(cs[0]); 131 | let cnameList = parseHtml(cs[1]); 132 | choiceObj['schema:name'] = cnameList; 133 | choiceList.push(choiceObj); 134 | 135 | }); 136 | // insert 'choices' key inside responseOptions 137 | if (rowData.hasOwnProperty('responseOptions')) { 138 | rowData.responseOptions[schemaMap[current_key]] = choiceList; 139 | } 140 | else { 141 | rspObj[schemaMap[current_key]] = choiceList; 142 | rowData['responseOptions'] = rspObj; 143 | } 144 | } 145 | // check all response elements to be nested under 'responseOptions' key 146 | else if (responseList.indexOf(schemaMap[current_key]) > -1) { 147 | if (rowData.hasOwnProperty('responseOptions')) { 148 | rowData.responseOptions[schemaMap[current_key]] = data[current_key]; 149 | } 150 | else { 151 | rspObj[schemaMap[current_key]] = data[current_key]; 152 | rowData['responseOptions'] = rspObj; 153 | } 154 | } 155 | // branching logic 156 | else if (schemaMap[current_key] === 'branchLogic' & data[current_key] !== '') { 157 | // set ui.hidden for the item to true by default 158 | if (rowData.hasOwnProperty('ui')) { 159 | rowData.ui['hidden'] = true; 160 | } 161 | else { 162 | ui['hidden'] = true; 163 | rowData['ui'] = ui; 164 | } 165 | let condition = data[current_key]; 166 | let s = condition; 167 | // normalize the condition field to resemble javascript 168 | let re = RegExp(/\(([0-9]*)\)/g); 169 | condition = condition.replace(re, "___$1"); 170 | condition = condition.replace(/([^>|<])=/g, "$1 =="); 171 | condition = condition.replace(/\ and\ /g, " && "); 172 | condition = condition.replace(/\ or\ /g, " || "); 173 | re = RegExp(/\[([^\]]*)\]/g); 174 | condition = condition.replace(re, " $1 "); 175 | let bl = (`if ( ${condition} ) { ${data['Variable / Field Name']}.ui.hidden = false }`); 176 | blObj.push(bl); 177 | } 178 | // decode html fields 179 | else if ((schemaMap[current_key] === 'question' || schemaMap[current_key] ==='schema:description') & data[current_key] !== '') { 180 | let questions = parseHtml(data[current_key]); 181 | rowData[schemaMap[current_key]] = questions; 182 | } 183 | // non-nested schema elements 184 | else if (data[current_key] !== '') 185 | rowData[schemaMap[current_key]] = data[current_key]; 186 | } 187 | // insert current_key in schema for non-existing mapping 188 | else rowData[camelcase(current_key)] = data[current_key]; 189 | } 190 | }); 191 | ins_name = data['Form Name']; 192 | const field_name = data['Variable / Field Name']; 193 | order.push(field_name); 194 | // write to item_x file 195 | fs.writeFileSync('activities/' + ins_name + '/items/' + field_name + '.jsonld', JSON.stringify(rowData, null, 4)); 196 | graphArr.push(rowData); 197 | } 198 | 199 | function finishSchemaCreation() { 200 | let jsonLD = { 201 | "@context": [schemaContextUrl, formContextUrl], 202 | "@type": "https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Activity.jsonld", 203 | "@id": ins_name + '_schema', 204 | "skos:prefLabel": ins_name + '_schema', 205 | "skos:altLabel": ins_name + '_schema', 206 | "schema:description": ins_name + ' schema', 207 | "schema:schemaVersion": "0.0.1", 208 | "schema:version": "0.0.1", 209 | "branchLogic": { 210 | "javascript": blObj 211 | }, 212 | "scoringLogic": { 213 | "javascript": "" 214 | }, 215 | "ui": { 216 | "order": order, 217 | "shuffle": false 218 | } 219 | }; 220 | const op = JSON.stringify(jsonLD, null, 4); 221 | fs.writeFile('activities/family_history_assessment_parent/' + ins_name + '_schema' + '.jsonld', op, function (err) { 222 | console.log("Instrument schema created"); 223 | }); 224 | } 225 | 226 | function parseLanguageIsoCodes(inputString){ 227 | let languages = []; 228 | const root = HTMLParser.parse(inputString); 229 | if(root.childNodes.length > 0 && inputString.indexOf('lang') !== -1){ 230 | if(root.childNodes){ 231 | root.childNodes.forEach(htmlElement => { 232 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 233 | languages.push(htmlElement.rawAttributes.lang) 234 | } 235 | }); 236 | } 237 | } 238 | return languages; 239 | } 240 | 241 | function parseHtml(inputString) { 242 | let result = {}; 243 | const root = HTMLParser.parse(inputString); 244 | if(root.childNodes.length > 0 ){ 245 | if (root.childNodes) { 246 | root.childNodes.forEach(htmlElement => { 247 | if(htmlElement.text) { 248 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 249 | result[htmlElement.rawAttributes.lang] = htmlElement.text; 250 | } else { 251 | result[defaultLanguage] = htmlElement.text; 252 | } 253 | } 254 | }); 255 | } 256 | } 257 | else { 258 | result[defaultLanguage] = inputString; 259 | } 260 | return result; 261 | } 262 | 263 | -------------------------------------------------------------------------------- /tools/create_neurovault_schema.py: -------------------------------------------------------------------------------- 1 | # this script takes the content of the metadata csv file from neurvovault and turns 2 | # it into a Repronim compliant schema 3 | 4 | # tested with python 3.7 5 | 6 | import json 7 | import os 8 | import csv 9 | 10 | # where the metadata from neurovault are described. Can be downloaded from here:v 11 | # https://github.com/NeuroVault/NeuroVault/blob/master/scripts/metadata_neurovault.csv 12 | input_file = '/home/remi/github/COBIDAS_chckls/xlsx/metadata_neurovault.csv' 13 | 14 | # where the files will be written (the local repo of the schema-standardization) 15 | output_dir = '/home/remi/github/schema-standardization' 16 | 17 | # placeholder to insert in all instances of the remote repo 18 | remote_repo = 'https://raw.githubusercontent.com/Remi-Gau/schema-standardization/' 19 | 20 | # to which branch of schema-standardization the ui will be pointed to 21 | branch_name = 'neurovault' 22 | 23 | 24 | # activity set names 25 | activity_set_schema_filename = 'cobidas_schema.jsonld' 26 | activity_set_context_filename = 'cobidas_context.jsonld' 27 | activity_set_folder_name = 'cobidas' 28 | 29 | 30 | # version 31 | version = '0.0.1' 32 | 33 | # make output directories 34 | if not os.path.exists(os.path.join(output_dir, 'activity-sets', activity_set_folder_name)): 35 | os.makedirs(os.path.join(output_dir, 'activity-sets', activity_set_folder_name)) 36 | 37 | 38 | 39 | # define the activity set neurovault_schema.jsonld 40 | nv_set_schema_json = { 41 | '@context': [ remote_repo + branch_name + '/contexts/generic.jsonld', 42 | remote_repo + branch_name + '/activity-sets/' + activity_set_folder_name + '/' + activity_set_context_filename 43 | ], 44 | '@type': remote_repo + branch_name + '/schemas/ActivitySet.jsonld', 45 | '@id': 'cobidas_schema', 46 | 'skos:prefLabel': 'neurovault as a COBIDAS POC', 47 | 'skos:altLabel': 'neurovault_COBIDAS_POC', 48 | 'schema:description': 'neurovault as a COBIDAS checklist proof of concept', 49 | 'schema:schemaVersion': version, 50 | 'schema:version': version, 51 | 'variableMap': [], 52 | 'ui': { 53 | 'order': [], 54 | 'shuffle': False, 55 | 'activity_display_name': {}, 56 | 'visibility': {} 57 | } 58 | } 59 | 60 | # define the activity set neurovault_context.jsonld 61 | nv_set_context_json = { 62 | '@context': { 63 | '@version': 1.1, 64 | 'activity_path': remote_repo + branch_name + '/activities/', 65 | } 66 | } 67 | 68 | 69 | Section = '' 70 | # loop through rows of the csv file and create corresponding jsonld for each item 71 | with open(input_file, 'r') as csvfile: 72 | nv_metadata = csv.reader(csvfile) 73 | for row in nv_metadata: 74 | 75 | # to skip the header 76 | if row[2]!='Item': 77 | 78 | # detect if this is a new section if so it will create a new activity 79 | if row[1]!=Section: 80 | 81 | # update section name 82 | Section=row[1] 83 | 84 | # where the items of this section will be stored 85 | activity_folder_name = 'Neurovault_' + Section 86 | 87 | # names of this section schema and its corresponding jsonld files 88 | activity_schema_name = 'Neurovault_' + Section + '_schema' 89 | 90 | activity_schema_filename = activity_schema_name + '.jsonld' 91 | 92 | activity_context_filename = 'Neurovault_' + Section + '_context.jsonld' 93 | 94 | 95 | print(activity_schema_name) 96 | 97 | # create dir for this section 98 | if not os.path.exists(os.path.join(output_dir, 'activities', activity_folder_name)): 99 | os.makedirs(os.path.join(output_dir, 'activities', activity_folder_name)) 100 | 101 | if not os.path.exists(os.path.join(output_dir, 'activities', activity_folder_name, 'items')): 102 | os.makedirs(os.path.join(output_dir, 'activities', activity_folder_name, 'items')) 103 | 104 | 105 | # define the base json content for the activity: neurovault_schema.jsonld neurovault_context.jsonld 106 | nv_context_json = { 107 | '@context': { 108 | '@version': 1.1, 109 | 'item_path': remote_repo + branch_name + '/activities/' + activity_folder_name + '/items/', 110 | } 111 | } 112 | 113 | nv_schema_json = { 114 | '@context': [ remote_repo + branch_name + '/contexts/generic.jsonld', 115 | remote_repo + branch_name + '/activities/' + activity_folder_name + '/' + activity_context_filename 116 | ], 117 | '@type': remote_repo + branch_name + '/schemas/Activity.jsonld', 118 | '@id': activity_schema_name, 119 | 'skos:prefLabel': 'COBIDAS design checklist', 120 | 'skos:altLabel': 'cobidas_design_schema', 121 | 'schema:description': 'COBIDAS design checklist schema', 122 | 'schema:schemaVersion': version, 123 | 'schema:version': version, 124 | 'preamble': 'How did you design/analyse your study?', 125 | 'ui': { 126 | 'order': [], 127 | 'shuffle': False 128 | } 129 | } 130 | 131 | # update the json content of the activity set schema and context wrt this new activity 132 | nv_set_schema_json['variableMap'].append( 133 | {'variableName': activity_schema_name,'isAbout': activity_schema_name} 134 | ) 135 | 136 | nv_set_schema_json['ui']['order'].append(activity_schema_name) 137 | nv_set_schema_json['ui']['visibility'][activity_schema_name] = True 138 | nv_set_schema_json['ui']['activity_display_name'][activity_schema_name] = 'Neurovault - ' + Section 139 | 140 | nv_set_context_json['@context'][activity_schema_name] = { 141 | '@id': 'activity_path:' + activity_folder_name + '/' + activity_schema_filename, 142 | '@type': '@id' 143 | } 144 | 145 | print(' ' + row[2]) 146 | 147 | 148 | # update the json content of the activity schema and context wrt this new item 149 | nv_schema_json['ui']['order'].append(row[2]) 150 | 151 | nv_context_json['@context'][row[2]] = { 152 | '@id': 'item_path:'+row[2]+'.jsonld', 153 | '@type': '@id' 154 | } 155 | 156 | # save activity jsonld with every new item 157 | with open(os.path.join(output_dir, 'activities', activity_folder_name, activity_schema_filename), 'w') as ff: 158 | json.dump(nv_schema_json, ff, sort_keys=False, indent=4) 159 | 160 | with open(os.path.join(output_dir, 'activities', activity_folder_name, activity_context_filename), 'w') as ff: 161 | json.dump(nv_context_json, ff, sort_keys=False, indent=4) 162 | 163 | 164 | # define jsonld for this item 165 | item_json = { 166 | '@context': [ remote_repo + branch_name + '/contexts/generic.jsonld', 167 | remote_repo + branch_name + '/activities/' + activity_folder_name + '/' + activity_context_filename 168 | ], 169 | '@type': remote_repo + branch_name + '/schemas/Field.jsonld', 170 | '@id': row[2], 171 | 'skos:prefLabel': row[2], 172 | 'skos:altLabel': row[2], 173 | 'schema:description': row[2], 174 | 'schema:schemaVersion': version, 175 | 'schema:version': version, 176 | 'question': row[3], 177 | } 178 | 179 | # now we define the answers for this item 180 | if row[4]=='Boolean': 181 | 182 | item_json['ui'] = { 183 | 'inputType': 'radio' 184 | } 185 | item_json['responseOptions'] = { 186 | '@type': 'xsd:anyURI', 187 | 'multipleChoice': False, 188 | 'schema:minValue': 0, 189 | 'schema:maxValue': 1, 190 | 'choices': [ 191 | { 192 | '@type': 'schema:Boolean', 193 | 'schema:name': 'no', 194 | 'schema:value': 0, 195 | }, 196 | { 197 | '@type': 'schema:Boolean', 198 | 'schema:name': 'yes', 199 | 'schema:value': 1, 200 | } 201 | ] 202 | } 203 | 204 | # if we have multiple choices 205 | elif row[4][0]=='[': 206 | 207 | # we get all the possible options and add them to the possible responses 208 | options = row[4][1:-2].replace("'", "").split(',') 209 | 210 | item_json['ui'] = { 211 | 'inputType': 'radio' 212 | } 213 | 214 | item_json['responseOptions'] = { 215 | '@type': 'xsd:anyURI', 216 | 'multipleChoice': False, 217 | 'schema:minValue': 0, 218 | 'schema:maxValue': len(options)-1, 219 | 'choices': [] 220 | } 221 | 222 | for i, opt in enumerate(options): 223 | 224 | item_json['responseOptions']['choices'].append({ 225 | 'schema:name': {'en': opt}, 226 | 'schema:value': i, 227 | }) 228 | 229 | 230 | # response is some integer 231 | elif row[4]=='int': 232 | item_json['ui'] = { 233 | 'inputType': 'text' 234 | } 235 | item_json['responseOptions'] = { 236 | 'type': 'xsd:int', 237 | } 238 | 239 | 240 | # response is some integer 241 | elif row[4]=='float': 242 | item_json['ui'] = { 243 | 'inputType': 'text' 244 | } 245 | item_json['responseOptions'] = { 246 | 'type': 'xsd:float', 247 | } 248 | 249 | 250 | # input requires some typed answer 251 | elif row[4]=='char': 252 | item_json['ui'] = { 253 | 'inputType': 'text' 254 | } 255 | item_json['responseOptions'] = { 256 | 'type': 'xsd:string' 257 | } 258 | 259 | else: 260 | item_json['ui'] = { 261 | 'inputType': 'text' 262 | } 263 | item_json['responseOptions'] = { 264 | 'type': 'xsd:string' 265 | } 266 | 267 | 268 | # write item jsonld 269 | with open(os.path.join(output_dir, 'activities', activity_folder_name, 'items', row[2] + '.jsonld'), 'w') as ff: 270 | json.dump(item_json, ff, sort_keys=False, indent=4) 271 | 272 | 273 | # write activity set jsonld 274 | with open(os.path.join(output_dir, 'activity-sets', activity_set_folder_name, activity_set_schema_filename), 'w') as ff: 275 | json.dump(nv_set_schema_json, ff, sort_keys=False, indent=4) 276 | 277 | with open(os.path.join(output_dir, 'activity-sets', activity_set_folder_name, activity_set_context_filename), 'w') as ff: 278 | json.dump(nv_set_context_json, ff, sort_keys=False, indent=4) 279 | -------------------------------------------------------------------------------- /tools/maltreatmentRedCap2schema.js: -------------------------------------------------------------------------------- 1 | /* ************ Constants **************************************************** */ 2 | const csv = require('fast-csv'); 3 | const fs = require('fs'); 4 | const shell = require('shelljs'); 5 | const camelcase = require('camelcase'); 6 | const mkdirp = require('mkdirp'); 7 | const HTMLParser = require ('node-html-parser'); 8 | 9 | const schemaMap = { 10 | "Identifier?": "@id", 11 | "Variable / Field Name": "skos:altLabel", 12 | "Field Note": "schema:description", 13 | "Section Header": "preamble", 14 | "Field Label": "question", 15 | "Field Type": "inputType", 16 | "Required Field?": "requiredValue", 17 | "Text Validation Min": "minValue", 18 | "Text Validation Max": "maxValue", 19 | "Choices, Calculations, OR Slider Labels": "choices", 20 | "Branching Logic (Show field only if...)": "branchLogic" 21 | } 22 | const uiList = ['inputType', 'shuffle']; 23 | const responseList = ['type', 'minValue', 'maxValue', 'requiredValue', 'multipleChoice']; 24 | const defaultLanguage = 'en'; 25 | const datas = {}; 26 | /* **************************************************************************************** */ 27 | 28 | // Make sure we got a filename on the command line. 29 | if (process.argv.length < 3) { 30 | console.log('Usage: node ' + process.argv[1] + ' FILENAME'); 31 | process.exit(1); 32 | } 33 | // Read the file. 34 | let csvPath = process.argv[2]; 35 | let readStream = fs.createReadStream(csvPath).setEncoding('utf-8'); 36 | 37 | let schemaContextUrl = 'https://raw.githubusercontent.com/ReproNim/schema-standardization/master/contexts/generic.jsonld'; 38 | let order = {}; 39 | let blList = []; 40 | let slList = []; 41 | let blObj = []; 42 | let languages = []; 43 | 44 | let options = { 45 | delimiter: ',', 46 | headers: true, 47 | objectMode: true, 48 | quote: '"', 49 | escape: '"', 50 | ignoreEmpty: true 51 | }; 52 | 53 | // get all field names and instrument name 54 | csv 55 | .fromStream(readStream, options) 56 | .on('data', function (data) { 57 | if (!datas[data['Form Name']]) { 58 | datas[data['Form Name']] = []; 59 | // For each form, create directory structure - activities/form_name/items 60 | shell.mkdir('-p', 'activities/' + data['Form Name'] + '/items'); 61 | } 62 | // console.log(62, data); 63 | datas[data['Form Name']].push(data); 64 | }) 65 | .on('end', function () { 66 | // console.log(66, datas); 67 | Object.keys(datas).forEach(form => { 68 | let fieldList = datas[form]; 69 | createFormContextSchema(form, fieldList); 70 | let formContextUrl = `https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/${form}/${form}_context.jsonld`; 71 | fieldList.forEach( field => { 72 | if(languages.length === 0){ 73 | languages = parseLanguageIsoCodes(field['Field Label']); 74 | } 75 | processRow(form, field); 76 | }); 77 | 78 | createFormSchema(form, formContextUrl); 79 | }); 80 | }); 81 | 82 | function createFormContextSchema(form, fieldList) { 83 | // define context file for each form 84 | let itemOBj = { "@version": 1.1 }; 85 | let formContext = {}; 86 | itemOBj[form] = `https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/${form}/items/`; 87 | fieldList.forEach( field => { 88 | let field_name = field['Variable / Field Name']; 89 | // define item_x urls to be inserted in context for the corresponding form 90 | itemOBj[field_name] = { "@id": `${form}:${field_name}.jsonld` , "@type": "@id" }; 91 | }); 92 | formContext['@context'] = itemOBj; 93 | const fc = JSON.stringify(formContext, null, 4); 94 | fs.writeFile(`activities/${form}/${form}_context.jsonld`, fc, function(err) { 95 | if (err) 96 | console.log(err); 97 | else console.log(`Context created for form ${form}`); 98 | }); 99 | } 100 | 101 | function processRow(form, data){ 102 | let rowData = {}; 103 | let ui = {}; 104 | let rspObj = {}; 105 | let choiceList = []; 106 | rowData['@context'] = [schemaContextUrl]; 107 | rowData['@type'] = 'https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld'; 108 | 109 | // map Choices, Calculations, OR Slider Labels column to choices or scoringLogic key 110 | if (data['Field Type'] === 'calc') 111 | schemaMap['Choices, Calculations, OR Slider Labels'] = 'scoringLogic'; 112 | else schemaMap['Choices, Calculations, OR Slider Labels'] = 'choices'; 113 | 114 | //console.log(110, schemaMap); 115 | Object.keys(data).forEach(current_key => { 116 | 117 | // get schema key from mapping.json corresponding to current_key 118 | if (schemaMap.hasOwnProperty(current_key)) { 119 | // if (schemaMap[current_key] === 'scoringLogic' && data[current_key] !== '') 120 | 121 | // check all ui elements to be nested under 'ui' key 122 | if (uiList.indexOf(schemaMap[current_key]) > -1) { 123 | let uiValue = data[current_key]; 124 | if (current_key === 'Field Type' && data[current_key] === 'calc') 125 | uiValue = 'number'; 126 | 127 | if (rowData.hasOwnProperty('ui')) { 128 | rowData.ui[schemaMap[current_key]] = uiValue; 129 | } 130 | else { 131 | ui[schemaMap[current_key]] = uiValue; 132 | rowData['ui'] = ui; 133 | } 134 | } 135 | // parse choice field 136 | else if (schemaMap[current_key] === 'choices' & data[current_key] !== '') { 137 | 138 | // split string wrt '|' to get each choice 139 | let c = data[current_key].split('|'); 140 | // split each choice wrt ',' to get schema:name and schema:value 141 | c.forEach(ch => { 142 | let choiceObj = {}; 143 | let cs = ch.split(', '); 144 | // create name and value pair for each choice option 145 | choiceObj['schema:value'] = parseInt(cs[0]); 146 | let cnameList = parseHtml(cs[1]); 147 | choiceObj['schema:name'] = cnameList; 148 | choiceList.push(choiceObj); 149 | 150 | }); 151 | // insert 'choices' key inside responseOptions 152 | if (rowData.hasOwnProperty('responseOptions')) { 153 | rowData.responseOptions[schemaMap[current_key]] = choiceList; 154 | } 155 | else { 156 | rspObj[schemaMap[current_key]] = choiceList; 157 | rowData['responseOptions'] = rspObj; 158 | } 159 | } 160 | // check all other response elements to be nested under 'responseOptions' key 161 | else if (responseList.indexOf(schemaMap[current_key]) > -1) { 162 | if (rowData.hasOwnProperty('responseOptions')) { 163 | rowData.responseOptions[schemaMap[current_key]] = data[current_key]; 164 | } 165 | else { 166 | rspObj[schemaMap[current_key]] = data[current_key]; 167 | rowData['responseOptions'] = rspObj; 168 | } 169 | } 170 | // scoring logic 171 | else if (schemaMap[current_key] === 'scoringLogic' && data[current_key] !== '') { 172 | // set ui.hidden for the item to true by default 173 | if (rowData.hasOwnProperty('ui')) { 174 | rowData.ui['hidden'] = true; 175 | } 176 | else { 177 | ui['hidden'] = true; 178 | rowData['ui'] = ui; 179 | } 180 | let condition = data[current_key]; 181 | let s = condition; 182 | // normalize the condition field to resemble javascript 183 | let re = RegExp(/\(([0-9]*)\)/g); 184 | condition = condition.replace(re, "___$1"); 185 | condition = condition.replace(/([^>|<])=/g, "$1 =="); 186 | condition = condition.replace(/\ and\ /g, " && "); 187 | condition = condition.replace(/\ or\ /g, " || "); 188 | re = RegExp(/\[([^\]]*)\]/g); 189 | condition = condition.replace(re, " $1 "); 190 | let sl = `${data['Variable / Field Name']} = ${condition}`; 191 | slList.push(sl); 192 | } 193 | // branching logic 194 | else if (schemaMap[current_key] === 'branchLogic' & data[current_key] !== '') { 195 | // set ui.hidden for the item to true by default 196 | if (rowData.hasOwnProperty('ui')) { 197 | rowData.ui['hidden'] = true; 198 | } 199 | else { 200 | ui['hidden'] = true; 201 | rowData['ui'] = ui; 202 | } 203 | let condition = data[current_key]; 204 | let s = condition; 205 | // normalize the condition field to resemble javascript 206 | let re = RegExp(/\(([0-9]*)\)/g); 207 | condition = condition.replace(re, "___$1"); 208 | condition = condition.replace(/([^>|<])=/g, "$1 =="); 209 | condition = condition.replace(/\ and\ /g, " && "); 210 | condition = condition.replace(/\ or\ /g, " || "); 211 | re = RegExp(/\[([^\]]*)\]/g); 212 | condition = condition.replace(re, " $1 "); 213 | let bl = (`if ( ${condition} ) { ${data['Variable / Field Name']}.ui.hidden = false }`); 214 | blList.push(bl); 215 | } 216 | // decode html fields 217 | else if ((schemaMap[current_key] === 'question' || schemaMap[current_key] ==='schema:description') & data[current_key] !== '') { 218 | let questions = parseHtml(data[current_key]); 219 | rowData[schemaMap[current_key]] = questions; 220 | } 221 | // non-nested schema elements 222 | else if (data[current_key] !== '') 223 | rowData[schemaMap[current_key]] = data[current_key]; 224 | } 225 | // insert non-existing mapping as is 226 | else rowData[camelcase(current_key)] = data[current_key]; 227 | }); 228 | const field_name = data['Variable / Field Name']; 229 | if (!order[form]) { 230 | order[form] = []; 231 | order[form].push(field_name); 232 | } 233 | else order[form].push(field_name); 234 | // write to item_x file 235 | fs.writeFile('activities/' + form + '/items/' + field_name + '.jsonld', JSON.stringify(rowData, null, 4), function (err) { 236 | if (err) { 237 | console.log("error in writing item schema", err); 238 | } 239 | }); 240 | } 241 | 242 | function createFormSchema(form, formContextUrl) { 243 | let jsonLD = { 244 | "@context": [schemaContextUrl, formContextUrl], 245 | "@type": "https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Activity.jsonld", 246 | "@id": `${form}_schema`, 247 | "skos:prefLabel": `${form }_schema`, 248 | "skos:altLabel": `${form}_schema`, 249 | "schema:description": `${form} schema`, 250 | "schema:schemaVersion": "0.0.1", 251 | "schema:version": "0.0.1", 252 | "branchLogic": { 253 | "javascript": blList 254 | }, 255 | "scoringLogic": { 256 | "javascript": slList 257 | }, 258 | "ui": { 259 | "order": order[form], 260 | "shuffle": false 261 | } 262 | }; 263 | const op = JSON.stringify(jsonLD, null, 4); 264 | // console.log(269, jsonLD); 265 | fs.writeFile(`activities/${form}/${form}_schema.jsonld`, op, function (err) { 266 | if (err) { 267 | console.log("error in writing form schema", err) 268 | } 269 | else console.log("Instrument schema created"); 270 | }); 271 | } 272 | 273 | function parseLanguageIsoCodes(inputString){ 274 | let languages = []; 275 | const root = HTMLParser.parse(inputString); 276 | if(root.childNodes.length > 0 && inputString.indexOf('lang') !== -1){ 277 | if(root.childNodes){ 278 | root.childNodes.forEach(htmlElement => { 279 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 280 | languages.push(htmlElement.rawAttributes.lang) 281 | } 282 | }); 283 | } 284 | } 285 | return languages; 286 | } 287 | 288 | function parseHtml(inputString) { 289 | let result = {}; 290 | const root = HTMLParser.parse(inputString); 291 | if(root.childNodes.length > 0 ){ 292 | if (root.childNodes) { 293 | root.childNodes.forEach(htmlElement => { 294 | if(htmlElement.text) { 295 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 296 | result[htmlElement.rawAttributes.lang] = htmlElement.text; 297 | } else { 298 | result[defaultLanguage] = htmlElement.text; 299 | } 300 | } 301 | }); 302 | } 303 | } 304 | else { 305 | result[defaultLanguage] = inputString; 306 | } 307 | return result; 308 | } 309 | 310 | -------------------------------------------------------------------------------- /tools/HBN2schema.js: -------------------------------------------------------------------------------- 1 | // create your raw github repo URL 2 | const userName = 'sanuann'; 3 | const repoName = 'reproschema'; 4 | const branchName = 'master'; 5 | 6 | let yourRepoURL = `https://raw.githubusercontent.com/${userName}/${repoName}/${branchName}`; 7 | 8 | /* ************ Constants **************************************************** */ 9 | const csv = require('fast-csv'); 10 | const fs = require('fs'); 11 | const shell = require('shelljs'); 12 | const camelcase = require('camelcase'); 13 | const HTMLParser = require ('node-html-parser'); 14 | 15 | const schemaMap = { 16 | 'Instructions': 'preamble', 17 | //'Question Group Instruction': 'preamble', 18 | 'Question (number optionally included)': 'question', 19 | 'Question ID': '@id', 20 | 'Response Type': 'inputType', 21 | 'Response Options': 'choices', 22 | 'Branching logic': 'visibility', 23 | }; 24 | const uiInputTypes = { 25 | 'single choice': 'radio', 26 | 'multiple choice': 'radio', 27 | 'text entry': 'text', 28 | 'text entry.': 'text', 29 | 'text entry. ': 'text', 30 | 'numeric': 'number' 31 | }; 32 | const uiList = ['inputType', 'shuffle', 'allow', 'customAlignment']; 33 | const responseList = ['valueType', 'minValue', 'maxValue', 'requiredValue', 'multipleChoice']; 34 | const defaultLanguage = 'en'; 35 | const datas = {}; 36 | const sectionOrderObj = {}; 37 | const sectionVariableMap = {}; 38 | const sectionVisObj = {}; 39 | const preambleObj = {}; 40 | 41 | /* **************************************************************************************** */ 42 | 43 | // Make sure we got a filename on the command line. 44 | if (process.argv.length < 3) { 45 | console.log('Usage: node ' + process.argv[1] + ' FILENAME'); 46 | process.exit(1); 47 | } 48 | // Read the file. 49 | let csvPath = process.argv[2]; 50 | let readStream = fs.createReadStream(csvPath).setEncoding('utf-8'); 51 | 52 | let schemaContextUrl = 'https://raw.githubusercontent.com/ReproNim/schema-standardization/master/contexts/generic'; 53 | let currentForm = ''; 54 | let QInstructionList = []; 55 | let order = []; 56 | let blList = []; 57 | let visibilityObj = {}; 58 | let slList = []; 59 | let variableMap = []; 60 | let languages = []; 61 | let options = { 62 | delimiter: ',', 63 | headers: true, 64 | objectMode: true, 65 | quote: '"', 66 | escape: '"', 67 | ignoreEmpty: true 68 | }; 69 | let field_counter; 70 | let respVal; 71 | // get all field names and instrument name 72 | csv 73 | .fromStream(readStream, options) 74 | .on('data', function (data) { 75 | 76 | let Questionnaire = (data['Questionnaire Name']).replace(/[&\/\\#,+()$~%.'":*?<>{}, +]/g, ''); 77 | if (!datas[Questionnaire]) { 78 | field_counter = 0; 79 | datas[Questionnaire] = []; 80 | // For each form, create directory structure - activities/form_name/items 81 | shell.mkdir('-p', 'activities/' + Questionnaire + '/items'); 82 | } 83 | // create new Questionnaire ID when it is null 84 | if (data['Questionnaire ID'] === '') { 85 | data['Questionnaire ID'] = abbreviate(data['Questionnaire Name']); 86 | } 87 | field_counter = field_counter + 1; 88 | // create new Question ID when it is null 89 | if (data['Question ID'] === '') { 90 | data['Question ID'] = data['Questionnaire ID'] + '_' + field_counter; 91 | } 92 | 93 | datas[Questionnaire].push(data); 94 | 95 | // collect preamble for every form 96 | if (!preambleObj[Questionnaire]) { 97 | if (data['Instructions'] !== '') { 98 | preambleObj[Questionnaire] = {'Instructions': data['Instructions']}; 99 | } 100 | else preambleObj[Questionnaire] = {}; 101 | } 102 | 103 | // check sections and act accordingly 104 | if (data['Question Group Instruction'] !== '') { 105 | let section = (data['Question Group Instruction']).trim(); 106 | // set order of fields in section 107 | if (!sectionOrderObj[section]) { // for every new section 108 | sectionOrderObj[section] = []; 109 | sectionVariableMap[section] = []; 110 | sectionVisObj[section] = []; 111 | } 112 | sectionOrderObj[section].push(data['Question ID']); 113 | sectionVariableMap[section].push({'variableName': data['Question ID'], 'isAbout': data['Question ID']}); 114 | visibilityObj = processVisibility(data); 115 | sectionVisObj[section].push(visibilityObj); 116 | } 117 | }) 118 | .on('end', function () { 119 | // console.log(119, datas); 120 | Object.keys(datas).forEach(form => { 121 | order = []; 122 | slList = []; 123 | blList = []; 124 | variableMap = []; 125 | currentForm = form; 126 | let rowList = datas[form]; 127 | const activityDisplayName = rowList[0]['Questionnaire Name']; 128 | let sectionList = []; 129 | let sectionNumber = 1; 130 | let formContextUrl = `https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/${form}/${form}_context`; 131 | // define context schema object for each form 132 | let contextOBj = { "@version": 1.1 }; 133 | contextOBj[form] = `https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/${form}/`; 134 | rowList.forEach( row => { 135 | if(languages.length === 0){ 136 | languages = parseLanguageIsoCodes(row['Question (number optionally included)']); 137 | } 138 | let field_name = row['Question ID']; 139 | // check if Question Group Instruction exist 140 | if (row['Question Group Instruction'] !== '') { 141 | row['Question Group Instruction'] = (row['Question Group Instruction']).trim(); 142 | // collect preamble for the section too 143 | if (sectionList.indexOf(row['Question Group Instruction']) === -1) { // every new section 144 | sectionList.push(row['Question Group Instruction']); 145 | let sectionName = `${row['Questionnaire ID']}_Section${sectionNumber}`; 146 | preambleObj[form][sectionName] = row['Question Group Instruction']; 147 | // create section schema 148 | createFormSchema(sectionName, formContextUrl, sectionName, 0); 149 | contextOBj[sectionName] = { "@id": `${form}:${sectionName}_schema` , "@type": "@id" }; 150 | if (order.indexOf(sectionName) === -1) { 151 | order.push(sectionName); 152 | } 153 | // add section name to activity variableMap 154 | variableMap.push({"variableName": sectionName, "isAbout": sectionName}); 155 | 156 | // add visibility of section 157 | visibilityObj = processVisibility(row); 158 | blList.push({"variableName": sectionName, "isVis": visibilityObj.isVis}); 159 | 160 | sectionNumber++; 161 | } 162 | } 163 | else { 164 | order.push(field_name); 165 | // add field to variableMap 166 | variableMap.push({"variableName": field_name, "isAbout": field_name}); 167 | // add visibility of items 168 | visibilityObj = processVisibility(row); 169 | blList.push(visibilityObj); 170 | } 171 | // define item_x urls to be inserted in context for the corresponding form 172 | contextOBj[field_name] = { "@id": `${form}:items/${field_name}` , "@type": "@id" }; 173 | 174 | processRow(form, row); 175 | }); 176 | // write context schema to file 177 | let formContext = {'@context': contextOBj}; 178 | const fc = JSON.stringify(formContext, null, 4); 179 | fs.writeFile(`activities/${form}/${form}_context`, fc, function(err) { 180 | if (err) 181 | console.log(err); 182 | else console.log(`Context created for form ${form}`); 183 | }); 184 | // generate each form schema 185 | createFormSchema(form, formContextUrl, activityDisplayName, 1); 186 | }); 187 | }); 188 | 189 | function processVisibility(data) { 190 | let condition = true; // default visibility value for fields 191 | if (data['Branching logic']) { 192 | // let condition = true; 193 | condition = data['Branching logic']; 194 | // normalize the condition field to resemble javascript 195 | let re = RegExp(/\(([0-9]*)\)/g); 196 | condition = condition.replace(re, "___$1"); 197 | condition = condition.replace(/([^>|<])=/g, "$1 =="); 198 | condition = condition.replace(/\ and\ /g, " && "); 199 | condition = condition.replace(/\ or\ /g, " || "); 200 | re = RegExp(/\[([^\]]*)\]/g); 201 | condition = condition.replace(re, " $1 "); 202 | } 203 | visibilityObj = { "variableName": data['Question ID'], "isVis": condition }; 204 | return visibilityObj; 205 | } 206 | 207 | function processRow(form, row){ 208 | let rowData = {}; 209 | let ui = {}; 210 | let rspObj = {}; 211 | let choiceList = []; 212 | rowData['@context'] = [schemaContextUrl]; 213 | let field_name = row['Question ID']; 214 | rowData[schemaMap['Question ID']] = field_name; 215 | rowData['@type'] = 'reproschema:Field'; 216 | rowData['schema:schemaVersion'] = '0.0.1'; 217 | rowData['schema:version'] = '0.0.1'; 218 | 219 | Object.keys(row).forEach(current_key => { 220 | 221 | if (schemaMap.hasOwnProperty(current_key) && current_key !== 'Question ID') { 222 | 223 | // decode html fields 224 | if ((schemaMap[current_key] === 'question' || schemaMap[current_key] ==='schema:description') && row[current_key] !== '') { 225 | let questions = parseHtml(row[current_key]); 226 | rowData[schemaMap[current_key]] = questions; 227 | } 228 | 229 | // check all ui elements to be nested under 'ui' key 230 | else if (uiList.indexOf(schemaMap[current_key]) > -1 && row[current_key]) { 231 | let uiValue = (row[current_key]).toLowerCase(); 232 | if (uiValue === 'single choice' || uiValue === 'multiple choice') { 233 | if (uiValue === 'single choice') 234 | respVal = false; 235 | else if (uiValue === 'multiple choice') 236 | respVal = true; 237 | if (rowData.hasOwnProperty('responseOptions')) { 238 | rowData.responseOptions['multipleChoice'] = respVal; 239 | } 240 | else { 241 | rspObj['multipleChoice'] = respVal; 242 | rowData['responseOptions'] = rspObj; 243 | } 244 | } 245 | if (uiInputTypes.hasOwnProperty(uiValue)) 246 | uiValue = uiInputTypes[uiValue]; 247 | if (rowData.hasOwnProperty('ui')) { 248 | rowData.ui[schemaMap[current_key]] = uiValue; 249 | } 250 | else { 251 | ui[schemaMap[current_key]] = uiValue; 252 | rowData['ui'] = ui; 253 | } 254 | } 255 | 256 | // check all other response elements to be nested under 'responseOptions' key 257 | else if (responseList.indexOf(schemaMap[current_key]) > -1) { 258 | if (rowData.hasOwnProperty('responseOptions')) { 259 | rowData.responseOptions[schemaMap[current_key]] = row[current_key]; 260 | } 261 | else { 262 | rspObj[schemaMap[current_key]] = row[current_key]; 263 | rowData['responseOptions'] = rspObj; 264 | } 265 | } 266 | 267 | // scoring logic 268 | else if (schemaMap[current_key] === 'scoringLogic' && row[current_key] !== '') { 269 | let condition = row[current_key]; 270 | // normalize the condition field to resemble javascript 271 | let re = RegExp(/\(([0-9]*)\)/g); 272 | condition = condition.replace(re, "___$1"); 273 | condition = condition.replace(/([^>|<])=/g, "$1 =="); 274 | condition = condition.replace(/\ and\ /g, " && "); 275 | condition = condition.replace(/\ or\ /g, " || "); 276 | re = RegExp(/\[([^\]]*)\]/g); 277 | condition = condition.replace(re, " $1 "); 278 | let scoresObj = {"variableName": row['Question ID'], "jsExpression": condition}; 279 | slList.push(scoresObj); 280 | console.log(261, '@@@@@@@@@', slList); 281 | } 282 | // // branching logic 283 | // else if (schemaMap[current_key] === 'visibility') { 284 | // let condition = true; 285 | // if (row[current_key]) { 286 | // condition = row[current_key]; 287 | // // normalize the condition field to resemble javascript 288 | // let re = RegExp(/\(([0-9]*)\)/g); 289 | // condition = condition.replace(re, "___$1"); 290 | // condition = condition.replace(/([^>|<])=/g, "$1 =="); 291 | // condition = condition.replace(/\ and\ /g, " && "); 292 | // condition = condition.replace(/\ or\ /g, " || "); 293 | // re = RegExp(/\[([^\]]*)\]/g); 294 | // condition = condition.replace(re, " $1 "); 295 | // } 296 | // visibilityObj = { "variableName": row['Question ID'], "isVis": condition }; 297 | // blList.push(visibilityObj); 298 | // } 299 | 300 | 301 | // parse choice field 302 | else if (schemaMap[current_key] === 'choices' && row[current_key] !== '') { 303 | 304 | // split string wrt '|' to get each choice 305 | let c = row[current_key].split(','); 306 | // split each choice wrt ',' to get schema:name and schema:value 307 | c.forEach(ch => { 308 | let choiceObj = {}; 309 | let cs = ch.split('='); 310 | // create name and value pair for each choice option 311 | choiceObj['value'] = parseInt(cs[0]); 312 | let cnameList = parseHtml(cs[1]); 313 | choiceObj['name'] = cnameList; 314 | choiceList.push(choiceObj); 315 | 316 | }); 317 | // insert 'choices' key inside responseOptions 318 | if (rowData.hasOwnProperty('responseOptions')) { 319 | rowData.responseOptions[schemaMap[current_key]] = choiceList; 320 | } 321 | else { 322 | rspObj[schemaMap[current_key]] = choiceList; 323 | rowData['responseOptions'] = rspObj; 324 | } 325 | } 326 | 327 | // // non-nested schema elements 328 | // else if (row[current_key] !== '') 329 | // rowData[schemaMap[current_key]] = row[current_key]; 330 | } 331 | }); 332 | 333 | // write to item_x file 334 | fs.writeFile(`activities/${form}/items/${field_name}`, JSON.stringify(rowData, null, 4), function (err) { 335 | if (err) { 336 | console.log("error in writing item schema", err); 337 | } 338 | }); 339 | } 340 | 341 | function createFormSchema(activity, formContextUrl, prefLabel, formFlag) { 342 | let jsonLD = { 343 | "@context": [schemaContextUrl, formContextUrl], 344 | "@type": "reproschema:Activity", 345 | "@id": `${activity}_schema`, 346 | "skos:prefLabel": prefLabel, 347 | "schema:description": `${activity} schema`, 348 | "schema:schemaVersion": "0.0.1", 349 | "schema:version": "0.0.1", 350 | "preamble": "", 351 | "ui": { 352 | "shuffle": false 353 | }, 354 | }; 355 | if (formFlag) { // form schema 356 | if (preambleObj.hasOwnProperty(activity)) 357 | jsonLD.preamble = preambleObj[activity]['Instructions']; 358 | jsonLD.ui['order'] = order; 359 | jsonLD.variableMap = variableMap; 360 | if (blList.length) { 361 | // console.log(301, blList); 362 | jsonLD.ui.visibility = blList; 363 | } 364 | if (slList.length) { 365 | // console.log(318, activity, slList); 366 | jsonLD.scoringLogic = slList; 367 | } 368 | const op = JSON.stringify(jsonLD, null, 4); 369 | fs.writeFile(`activities/${activity}/${activity}_schema`, op, function (err) { 370 | if (err) { 371 | console.log("error in writing form schema", err) 372 | } 373 | else console.log("Instrument schema created"); 374 | }); 375 | } 376 | else { // section schema 377 | if (preambleObj[currentForm].hasOwnProperty([activity])) 378 | jsonLD.preamble = preambleObj[currentForm][activity]; 379 | let sectionorder = preambleObj[currentForm][activity]; 380 | jsonLD.ui['order'] = sectionOrderObj[sectionorder]; // section order 381 | jsonLD.ui.inputType = 'section'; 382 | if (sectionVisObj[sectionorder].length) { 383 | jsonLD.ui['visibility'] = sectionVisObj[sectionorder]; 384 | } 385 | jsonLD.variableMap = sectionVariableMap[sectionorder]; 386 | const op = JSON.stringify(jsonLD, null, 4); 387 | fs.writeFile(`activities/${currentForm}/${activity}_schema`, op, function (err) { 388 | if (err) { 389 | console.log("error in writing section schema", err) 390 | } 391 | else console.log("Section schema created"); 392 | }); 393 | } 394 | } 395 | 396 | function parseLanguageIsoCodes(inputString){ 397 | let languages = []; 398 | const root = HTMLParser.parse(inputString); 399 | if(root.childNodes.length > 0 && inputString.indexOf('lang') !== -1){ 400 | if(root.childNodes){ 401 | root.childNodes.forEach(htmlElement => { 402 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 403 | languages.push(htmlElement.rawAttributes.lang) 404 | } 405 | }); 406 | } 407 | } 408 | return languages; 409 | } 410 | 411 | function parseHtml(inputString) { 412 | let result = {}; 413 | const root = HTMLParser.parse(inputString); 414 | if(root.childNodes.length > 0 ){ 415 | if (root.childNodes) { 416 | root.childNodes.forEach(htmlElement => { 417 | if(htmlElement.text) { 418 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 419 | result[htmlElement.rawAttributes.lang] = htmlElement.text; 420 | } else { 421 | result[defaultLanguage] = htmlElement.text; 422 | } 423 | } 424 | }); 425 | } 426 | } 427 | else { 428 | result[defaultLanguage] = inputString; 429 | } 430 | return result; 431 | } 432 | 433 | function abbreviate(QName) { 434 | var result = QName.replace(/(\w)\w*\W*/g, function (_, i) { 435 | return i.toUpperCase(); 436 | } 437 | ) 438 | return result; 439 | } 440 | -------------------------------------------------------------------------------- /tools/RedCap2ReproSchema.js: -------------------------------------------------------------------------------- 1 | //User inputs: these are specific to your protocol, fill out before using the script 2 | 3 | //1. your protocol id: use underscore for spaces, avoid special characters. The display name is the one that will show up in the app, this will be parsed as string. 4 | const protocolName = "sc_dd"; 5 | 6 | //2. your protocol display name: this will show up in the app and be parsed as a string 7 | const protocolDisplayName = "Your protocol display name"; 8 | 9 | //2. create your raw github repo URL 10 | const userName = 'sanuann'; 11 | const repoName = 'reproschema'; 12 | const branchName = 'master'; 13 | 14 | let yourRepoURL = `https://raw.githubusercontent.com/${userName}/${repoName}/${branchName}`; 15 | 16 | //3. add a description to your protocol 17 | let protocolDescription = "Description for your protocol"; 18 | 19 | //4. where are you hosting your images? For example: openmoji 20 | let imagePath = 'https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/618x618/'; 21 | 22 | /* ************ Constants **************************************************** */ 23 | const csv = require('fast-csv'); 24 | const fs = require('fs'); 25 | const _ = require('lodash'); 26 | const shell = require('shelljs'); 27 | const camelcase = require('camelcase'); 28 | const mkdirp = require('mkdirp'); 29 | const HTMLParser = require ('node-html-parser'); 30 | 31 | const schemaMap = { 32 | "Variable / Field Name": "@id", // column A 33 | "Item Display Name": "prefLabel", 34 | "Field Annotation": "description", // column R 35 | "Section Header": "preamble", // todo: check this // column C 36 | "Field Label": "question", // column E 37 | "Field Type": "inputType", // column D 38 | "Allow": "allow", 39 | "Required Field?": "requiredValue", //column M 40 | "Text Validation Min": "minValue", // column I 41 | "Text Validation Max": "maxValue", // column J 42 | "Choices, Calculations, OR Slider Labels": "choices", // column F 43 | "Branching Logic (Show field only if...)": "visibility", // column L 44 | "Custom Alignment": "customAlignment", // column N 45 | "Identifier?": "identifiable", // column K 46 | "multipleChoice": "multipleChoice", 47 | "responseType": "@type" 48 | 49 | }; 50 | 51 | 52 | const inputTypeMap = { 53 | "calc": "number", 54 | "checkbox": "radio", 55 | "descriptive": "static", 56 | "dropdown": "select", 57 | "notes": "text" 58 | }; 59 | 60 | const uiList = ['inputType', 'shuffle', 'allow', 'customAlignment']; 61 | const responseList = ['valueType', 'minValue', 'maxValue', 'requiredValue', 'multipleChoice']; 62 | const additionalNotesList = ['Field Note', 'Question Number (surveys only)']; 63 | const defaultLanguage = 'en'; 64 | const datas = {}; 65 | const sectionOrderObj = {}; 66 | const sectionVariableMap = {}; 67 | const sectionVisObj = {}; 68 | const preambleObj = {}; 69 | 70 | /* **************************************************************************************** */ 71 | 72 | // Make sure we got a filename on the command line. 73 | if (process.argv.length < 3) { 74 | console.log('Usage: node ' + process.argv[1] + 'your_data_dic.csv'); 75 | process.exit(1); 76 | } 77 | 78 | // Read the CSV file. 79 | let csvPath = process.argv[2]; 80 | let readStream = fs.createReadStream(csvPath).setEncoding('utf-8'); 81 | 82 | let schemaContextUrl = 'https://raw.githubusercontent.com/ReproNim/reproschema/1.0.0-rc4/contexts/generic'; 83 | let order = []; 84 | let blList = []; 85 | let slList = []; 86 | let visibilityObj = {}; 87 | let scoresObj = {}; 88 | let scoresList = []; 89 | let visibilityList = []; 90 | let languages = []; 91 | let variableMap = []; 92 | let matrixList = []; 93 | let protocolVariableMap = []; 94 | let protocolVisibilityObj = {}; 95 | let protocolOrder = []; 96 | 97 | 98 | let options = { 99 | delimiter: ',', 100 | headers: true, 101 | objectMode: true, 102 | quote: '"', 103 | escape: '"', 104 | ignoreEmpty: true 105 | }; 106 | 107 | // get all field names and instrument name 108 | csv 109 | .fromStream(readStream, options) 110 | .on('data', function (data) { 111 | if (!datas[data['Form Name']]) { 112 | datas[data['Form Name']] = []; 113 | // For each form, create directory structure - activities/form_name/items 114 | shell.mkdir('-p', 'activities/' + data['Form Name'] + '/items'); 115 | } 116 | //create directory for protocol 117 | shell.mkdir('-p', 'protocols/' + protocolName); 118 | // console.log(62, data); 119 | datas[data['Form Name']].push(data); 120 | }) 121 | 122 | .on('end', function () { 123 | //console.log(66, datas); 124 | Object.keys(datas).forEach( form => { 125 | scoresList = []; 126 | order = []; 127 | slList = []; 128 | blList = []; 129 | visibilityList = []; 130 | let rowList = datas[form]; // all items of an activity 131 | // createFormContextSchema(form, rowList); // create context for each activity 132 | // let formContextUrl = `${yourRepoURL}/activities/${form}/${form}_context`; 133 | scoresObj = {}; 134 | visibilityObj = {}; 135 | variableMap = []; 136 | matrixList = []; 137 | //console.log(rowList[0]['Form Display Name']); 138 | activityDisplayName = rowList[0]['Form Name']; 139 | activityDescription = rowList[0]['Form Note']; 140 | rowList.forEach( field => { 141 | if(languages.length === 0){ 142 | languages = parseLanguageIsoCodes(field['Field Label']); 143 | } 144 | let field_name = field['Variable / Field Name']; 145 | // add visibility of items 146 | visibilityObj = processVisibility(field); 147 | blList.push(visibilityObj); 148 | 149 | // add field to variableMap 150 | variableMap.push({"variableName": field_name, "isAbout": `items/${field_name}`}); 151 | 152 | // add matrix info to matrixList 153 | if (field['Matrix Group Name'] || field['Matrix Ranking?']) { 154 | matrixList.push({"variableName": field_name, "matrixGroupName": field['Matrix Group Name'], "matrixRanking": field['Matrix' + 155 | ' Ranking?']}); 156 | } 157 | // check if 'order' object exists for the activity and add the items to the respective order array 158 | if (!order[form]) { 159 | order[form] = []; 160 | order[form].push(`items/${field_name}`); 161 | } 162 | else order[form].push(`items/${field_name}`); 163 | processRow(form, field); 164 | }); 165 | createFormSchema(form); 166 | }); 167 | //create protocol context 168 | let activityList = Object.keys(datas); 169 | // let protocolContextUrl = `${yourRepoURL}/protocols/${protocolName}/${protocolName}_context`; 170 | // createProtocolContext(activityList); 171 | 172 | //create protocol schema 173 | activityList.forEach( activityName => { 174 | processActivities(activityName); 175 | }); 176 | 177 | createProtocolSchema(protocolName); 178 | 179 | }); 180 | 181 | function createFormContextSchema(form, rowList) { 182 | // define context file for each form 183 | let itemOBj = { "@version": 1.1 }; 184 | let formContext = {}; 185 | itemOBj[form] = `${yourRepoURL}/activities/${form}/items/`; 186 | rowList.forEach( field => { 187 | let field_name = field['Variable / Field Name']; 188 | // define item_x urls to be inserted in context for the corresponding form 189 | itemOBj[field_name] = { "@id": `${form}:${field_name}` , "@type": "@id" }; 190 | }); 191 | formContext['@context'] = itemOBj; 192 | const fc = JSON.stringify(formContext, null, 4); 193 | fs.writeFile(`activities/${form}/${form}_context`, fc, function(err) { 194 | if (err) 195 | console.log(err); 196 | else console.log(`Context created for form ${form}`); 197 | }); 198 | } 199 | 200 | function createProtocolContext(activityList) { 201 | //create protocol context file 202 | let activityOBj = { "@version": 1.1, 203 | "activity_path": `${yourRepoURL}/activities/` 204 | }; 205 | let protocolContext = {}; 206 | activityList.forEach(activity => { 207 | //let activityName = activity['Form Name']; 208 | // define item_x urls to be inserted in context for the corresponding form 209 | activityOBj[activity] = { "@id": `activity_path:${activity}/${activity}_schema` , "@type": "@id" }; 210 | }); 211 | protocolContext['@context'] = activityOBj; 212 | const pc = JSON.stringify(protocolContext, null, 4); 213 | fs.writeFile(`protocols/${protocolName}/${protocolName}_context`, pc, function(err) { 214 | if (err) 215 | console.log(err); 216 | else console.log(`Protocol context created for ${protocolName}`); 217 | }); 218 | } 219 | 220 | function processVisibility(data) { 221 | let condition = true; // default visibility value for fields 222 | if (data['Branching Logic (Show field only if...)']) { 223 | // let condition = true; 224 | condition = data['Branching Logic (Show field only if...)']; 225 | // normalize the condition field to resemble javascript 226 | let re = RegExp(/\(([0-9]*)\)/g); 227 | condition = condition.replace(re, "___$1"); 228 | condition = condition.replace(/([^>|<])=/g, "$1 =="); 229 | condition = condition.replace(/\ and\ /g, " && "); 230 | condition = condition.replace(/\ or\ /g, " || "); 231 | re = RegExp(/\[([^\]]*)\]/g); 232 | condition = condition.replace(re, " $1 "); 233 | } 234 | visibilityObj = { "variableName": data['Variable / Field Name'], 235 | "isAbout": `items/${data['Variable / Field Name']}`, 236 | "isVis": condition }; 237 | return visibilityObj; 238 | } 239 | 240 | function processRow(form, data){ 241 | let rowData = {}; 242 | let ui = {}; 243 | let rspObj = {}; 244 | let choiceList = []; 245 | 246 | 247 | rowData['@context'] = schemaContextUrl; 248 | rowData['@type'] = 'reproschema:Field'; 249 | // rowData['@id'] = data['Variable / Field Name']; 250 | 251 | // map Choices, Calculations, OR Slider Labels column to choices or scoringLogic key 252 | if (data['Field Type'] === 'calc') 253 | schemaMap['Choices, Calculations, OR Slider Labels'] = 'scoringLogic'; 254 | else schemaMap['Choices, Calculations, OR Slider Labels'] = 'choices'; 255 | 256 | // parse Field Type and populate inputType and responseOptions valueType 257 | if (data['Field Type']) { 258 | let inputType = ''; 259 | let valueType = ''; 260 | if (data['Field Type'] === 'text') { 261 | if (data['Text Validation Type OR Show Slider Number']) { 262 | if (data['Text Validation Type OR Show Slider Number'] === 'number') { 263 | inputType = 'integer'; 264 | valueType = 'xsd:int'; 265 | } 266 | else if ((data['Text Validation Type OR Show Slider Number']).startsWith('date_')) { 267 | inputType = 'date'; 268 | valueType = 'xsd:date'; 269 | } 270 | else if (data['Text Validation Type OR Show Slider Number'].startsWith('datetime_')) { 271 | inputType = 'datetime'; 272 | valueType = 'datetime'; 273 | } 274 | else if (data['Text Validation Type OR Show Slider Number'].startsWith('time_')) { 275 | inputType = 'time'; 276 | valueType = 'xsd:date'; 277 | } 278 | else if (data['Text Validation Type OR Show Slider Number'] === 'email') { 279 | inputType = 'text'; 280 | valueType = 'email'; 281 | } 282 | else if (data['Text Validation Type OR Show Slider Number'] === 'phone') { 283 | inputType = 'text'; 284 | valueType = 'phone'; 285 | } 286 | } 287 | else { 288 | inputType = 'text'; 289 | valueType = 'xsd:string'; 290 | } 291 | } 292 | else if (data['Field Type'] === 'notes') { 293 | inputType = 'text'; 294 | valueType = 'xsd:string'; 295 | } 296 | else if (data['Field Type'] === 'calc') { 297 | inputType = 'float'; 298 | valueType = 'float'; 299 | } 300 | else if (data['Field Type'] === 'dropdown') { 301 | inputType = 'select'; 302 | valueType = ''; 303 | } 304 | else if (data['Field Type'] === 'radio') { 305 | inputType = 'radio'; 306 | multipleChoice = false; 307 | valueType = ''; 308 | } 309 | else if (data['Field Type'] === 'checkbox') { 310 | inputType = 'radio'; 311 | valueType = 'radio'; 312 | multipleChoice = true; 313 | } 314 | else if (data['Field Type'] === 'slider') { 315 | inputType = 'slider'; 316 | valueType = 'slider'; 317 | } 318 | else if (data['Field Type'] === 'file upload') { 319 | inputType = 'upload'; 320 | } 321 | else if (data['Field Type'] === 'descriptive') { 322 | inputType = 'static'; 323 | } 324 | rowData['ui'] = {'inputType': inputType}; 325 | if (valueType) { 326 | rowData['responseOptions'] = {'valueType': valueType}; 327 | } 328 | } 329 | 330 | Object.keys(data).forEach(current_key => { 331 | 332 | //Parse 'allow' array 333 | if (schemaMap[current_key] === 'allow' && data[current_key] !== '') { 334 | let uiKey = schemaMap[current_key]; 335 | let uiValue = data[current_key].split(', '); 336 | //uiValue.forEach(val => { 337 | // allowList.push(val) 338 | //}) 339 | // add object to ui element of the item 340 | if (rowData.hasOwnProperty('ui')) { 341 | rowData.ui[uiKey] = uiValue; // append to existing ui object 342 | } 343 | else { // create new ui object 344 | ui[uiKey] = uiValue; 345 | rowData['ui'] = ui; 346 | } 347 | } 348 | 349 | // check all ui elements to be nested under 'ui' key of the item 350 | else if (uiList.indexOf(schemaMap[current_key]) > -1 && data[current_key] !== '') { 351 | let uiKey = schemaMap[current_key]; 352 | let uiValue = data[current_key]; 353 | if (inputTypeMap.hasOwnProperty(data[current_key])) { // map Field type to supported inputTypes 354 | uiValue = inputTypeMap[data[current_key]]; 355 | } 356 | // else if ((uiKey === 'inputType') && (uiValue === 'text') && data['Text Validation Type OR Show Slider Number'] === 'number') { 357 | // uiValue = 'integer'; 358 | // valueType = 'xsd:int' 359 | // } 360 | // else if ((uiKey === 'inputType') && (uiValue === 'text') && data['Text Validation Type OR Show Slider Number'] === 'date_mdy') { 361 | // uiValue = 'date'; 362 | // valueType = 'xsd:date'; 363 | // } 364 | 365 | // add object to ui element of the item 366 | if (rowData.hasOwnProperty('ui')) { 367 | rowData.ui[uiKey] = uiValue; // append to existing ui object 368 | } 369 | else { // create new ui object 370 | ui[uiKey] = uiValue; 371 | rowData['ui'] = ui; 372 | } 373 | } 374 | 375 | // parse multipleChoice 376 | else if (schemaMap[current_key] === 'multipleChoice' && data[current_key] !== '') { 377 | 378 | // split string wrt '|' to get each choice 379 | let multipleChoiceVal = (data[current_key]) === '1'; 380 | 381 | // insert 'multiplechoices' key inside responseOptions of the item 382 | if (rowData.hasOwnProperty('responseOptions')) { 383 | rowData.responseOptions[schemaMap[current_key]] = multipleChoiceVal; 384 | } 385 | else { 386 | rspObj[schemaMap[current_key]] = multipleChoiceVal; 387 | rowData['responseOptions'] = rspObj; 388 | } 389 | } 390 | 391 | //parse minVal 392 | else if (schemaMap[current_key] === 'schema:minValue' && data[current_key] !== '') { 393 | 394 | 395 | let minValVal = (data[current_key]); 396 | 397 | // insert 'multiplechoices' key inside responseOptions of the item 398 | if (rowData.hasOwnProperty('responseOptions')) { 399 | rowData.responseOptions[schemaMap[current_key]] = minValVal; 400 | } 401 | else { 402 | rspObj[schemaMap[current_key]] = minValVal; 403 | rowData['responseOptions'] = rspObj; 404 | } 405 | } 406 | 407 | //parse maxVal 408 | else if (schemaMap[current_key] === 'schema:maxValue' && data[current_key] !== '') { 409 | let maxValVal = (data[current_key]); 410 | // insert 'multiplechoices' key inside responseOptions of the item 411 | if (rowData.hasOwnProperty('responseOptions')) { 412 | rowData.responseOptions[schemaMap[current_key]] = maxValVal; 413 | } 414 | else { 415 | rspObj[schemaMap[current_key]] = maxValVal; 416 | rowData['responseOptions'] = rspObj; 417 | } 418 | } 419 | /* 420 | //parse @type 421 | else if (schemaMap[current_key] === '@type') { 422 | 423 | // insert "@type":"xsd:anyURI" key inside responseOptions of the item 424 | if (rowData.hasOwnProperty('responseOptions')) { 425 | rowData.responseOptions[schemaMap[current_key]] = "xsd:anyURI"; 426 | } 427 | else { 428 | rspObj[schemaMap[current_key]] = "xsd:anyURI"; 429 | rowData['responseOptions'] = rspObj; 430 | } 431 | } 432 | */ 433 | 434 | // parse choice field 435 | else if (schemaMap[current_key] === 'choices' && data[current_key] !== '') { 436 | 437 | // split string wrt '|' to get each choice 438 | let c = data[current_key].split('|'); 439 | // split each choice wrt ',' to get schema:name and schema:value 440 | c.forEach(ch => { // ch = { value, name} 441 | let choiceObj = {}; 442 | let cs = ch.split(', '); 443 | // create name and value pair + image link for each choice option 444 | if (cs.length === 3) { 445 | choiceObj['schema:value'] = parseInt(cs[0]); 446 | let cnameList = cs[1]; 447 | choiceObj['schema:name'] = cnameList; 448 | choiceObj['@type'] = "schema:option"; 449 | choiceObj['schema:image'] = imagePath + cs[2] + '.png'; 450 | choiceList.push(choiceObj); 451 | } else { 452 | // for no image, create name and value pair for each choice option 453 | choiceObj['schema:value'] = parseInt(cs[0]); 454 | let cnameList = cs[1]; 455 | choiceObj['schema:name'] = cnameList; 456 | choiceObj['@type'] = "schema:option"; 457 | choiceList.push(choiceObj); 458 | } 459 | 460 | }); 461 | // insert 'choices' key inside responseOptions of the item 462 | if (rowData.hasOwnProperty('responseOptions')) { 463 | rowData.responseOptions[schemaMap[current_key]] = choiceList; 464 | } 465 | else { 466 | rspObj[schemaMap[current_key]] = choiceList; 467 | rowData['responseOptions'] = rspObj; 468 | } 469 | } 470 | 471 | // check all other response elements to be nested under 'responseOptions' key 472 | else if (responseList.indexOf(schemaMap[current_key]) > -1) { 473 | if (schemaMap[current_key] === 'requiredValue' && data[current_key]) { 474 | if (data[current_key] === 'y') { 475 | data[current_key] = true 476 | } 477 | } 478 | if (data[current_key]) { // if value exists in the column then write it to schema 479 | if (rowData.hasOwnProperty('responseOptions')) { 480 | rowData.responseOptions[schemaMap[current_key]] = data[current_key]; 481 | } 482 | else { 483 | rspObj[schemaMap[current_key]] = data[current_key]; 484 | rowData['responseOptions'] = rspObj; 485 | } 486 | } 487 | } 488 | // scoring logic 489 | else if (schemaMap[current_key] === 'scoringLogic' && data[current_key] !== '') { 490 | // set ui.hidden for the item to true by default 491 | if (rowData.hasOwnProperty('ui')) { 492 | rowData.ui['hidden'] = true; 493 | } 494 | else { 495 | ui['hidden'] = true; 496 | rowData['ui'] = ui; 497 | } 498 | let condition = data[current_key]; 499 | let s = condition; 500 | // normalize the condition field to resemble javascript 501 | let re = RegExp(/\(([0-9]*)\)/g); 502 | condition = condition.replace(re, "___$1"); 503 | condition = condition.replace(/([^>|<])=/g, "$1 =="); 504 | condition = condition.replace(/\ and\ /g, " && "); 505 | condition = condition.replace(/\ or\ /g, " || "); 506 | re = RegExp(/\[([^\]]*)\]/g); 507 | condition = condition.replace(re, " $1 "); 508 | 509 | scoresObj = { "variableName": data['Variable / Field Name'], "jsExpression": condition }; 510 | scoresList.push(scoresObj); 511 | } 512 | 513 | // branching logic 514 | else if (schemaMap[current_key] === 'visibility') { 515 | let condition = true; // for items visible by default 516 | if (data[current_key]) { 517 | condition = data[current_key]; 518 | let s = condition; 519 | // normalize the condition field to resemble javascript 520 | let re = RegExp(/\(([0-9]*)\)/g); 521 | condition = condition.replace(re, "___$1"); 522 | condition = condition.replace(/([^>|<])=/g, "$1=="); 523 | condition = condition.replace(/\ and\ /g, " && "); 524 | condition = condition.replace(/\ or\ /g, " || "); 525 | re = RegExp(/\[([^\]]*)\]/g); 526 | condition = condition.replace(re, "$1"); 527 | } 528 | visibilityObj = { "variableName": data['Variable / Field Name'], "isVis": condition }; 529 | visibilityList.push(visibilityObj); 530 | //visibilityObj[[data['Variable / Field Name']]] = condition; 531 | } 532 | 533 | // decode html fields 534 | else if ((schemaMap[current_key] === 'question' || schemaMap[current_key] ==='schema:description' 535 | || schemaMap[current_key] === 'preamble') && data[current_key] !== '') { 536 | let questions = parseHtml(data[current_key]); 537 | // console.log(231, form, schemaMap[current_key], questions); 538 | rowData[schemaMap[current_key]] = questions; 539 | } 540 | 541 | else if (current_key === 'Identifier?' && data[current_key]) { 542 | let identifierVal = false; 543 | if (data[current_key] === 'y') { 544 | identifierVal = true 545 | } 546 | // TODO: leave "legalStandard" to the user as an optional flag 547 | // if the user says its hipaa use that. if not leave it as "unknown" 548 | rowData[schemaMap[current_key]] = [ {"legalStandard": "unknown", "isIdentifier": identifierVal }]; 549 | } 550 | 551 | else if ((additionalNotesList.indexOf(current_key) > -1) && data[current_key]) { 552 | console.log(436, current_key, data[current_key]); 553 | let notesObj = {"source": "redcap", "column": current_key, "value": data[current_key]}; 554 | if (rowData.hasOwnProperty('additionalNotesObj')) { 555 | (rowData.additionalNotesObj).push(notesObj); 556 | } 557 | else { 558 | rowData['additionalNotesObj'] = []; 559 | (rowData['additionalNotesObj']).push(notesObj); 560 | } 561 | } 562 | 563 | 564 | // todo: what does "textValidationTypeOrShowSliderNumber": "number" mean along with inputType: "text" ? 565 | // text with no value in validation column is -- text inputType 566 | // text with value in validation as "number" is of inputType - integer 567 | // text with value in validation as ddate_mdy is of inputType - date 568 | // dropdown and autocomplete?? 569 | }); 570 | const field_name = data['Variable / Field Name']; 571 | 572 | // write to item_x file 573 | fs.writeFile('activities/' + form + '/items/' + field_name, JSON.stringify(rowData, null, 4), function (err) { 574 | if (err) { 575 | console.log("error in writing item schema", err); 576 | } 577 | }); 578 | } 579 | 580 | 581 | function createFormSchema(form) { 582 | // console.log(27, form, visibilityObj); 583 | // "skos:altLabel": `${form}_schema`, 584 | let jsonLD = { 585 | "@context": schemaContextUrl, 586 | "@type": "reproschema:Activity", 587 | "@id": `${form}_schema`, 588 | "prefLabel": activityDisplayName, 589 | "description": activityDescription, 590 | "schemaVersion": "1.0.0-rc4", 591 | "version": "0.0.1", 592 | // todo: preamble: Field Type = descriptive represents preamble in the CSV file., it also has branching logic. so should preamble be an item in our schema? 593 | "ui": { 594 | "order": order[form], 595 | "addProperties": blList, 596 | "shuffle": false 597 | } 598 | }; 599 | if (!_.isEmpty(matrixList)) { 600 | jsonLD['matrixInfo'] = matrixList; 601 | } 602 | if (!_.isEmpty(scoresList)) { 603 | jsonLD['scoringLogic'] = scoresList; 604 | } 605 | const op = JSON.stringify(jsonLD, null, 4); 606 | // console.log(269, jsonLD); 607 | fs.writeFile(`activities/${form}/${form}_schema`, op, function (err) { 608 | if (err) { 609 | console.log("error in writing", form, " form schema", err) 610 | } 611 | else console.log(form, "Instrument schema created"); 612 | }); 613 | } 614 | 615 | function processActivities (activityName) { 616 | 617 | let condition = true; // for items visible by default 618 | protocolVisibilityObj[activityName] = condition; 619 | 620 | // add activity to variableMap and Order 621 | protocolVariableMap.push({"variableName": activityName, "isAbout": `items/${activityName}`}); 622 | protocolOrder.push(activityName); 623 | 624 | } 625 | 626 | function createProtocolSchema(protocolName) { 627 | let protocolSchema = { 628 | "@context": schemaContextUrl, 629 | "@type": "reproschema:ActivitySet", 630 | "@id": `${protocolName}_schema`, 631 | "skos:prefLabel": protocolDisplayName, 632 | "skos:altLabel": `${protocolName}_schema`, 633 | "schema:description": protocolDescription, 634 | "schema:schemaVersion": "1.0.0-rc4", 635 | "schema:version": "0.0.1", 636 | // todo: preamble: Field Type = descriptive represents preamble in the CSV file., it also has branching logic. so should preamble be an item in our schema? 637 | "variableMap": protocolVariableMap, 638 | "ui": { 639 | "order": protocolOrder, 640 | "shuffle": false, 641 | "visibility": protocolVisibilityObj 642 | } 643 | }; 644 | const op = JSON.stringify(protocolSchema, null, 4); 645 | // console.log(269, jsonLD); 646 | fs.writeFile(`protocols/${protocolName}/${protocolName}_schema`, op, function (err) { 647 | if (err) { 648 | console.log("error in writing protocol schema") 649 | } 650 | else console.log("Protocol schema created"); 651 | }); 652 | 653 | } 654 | 655 | function parseLanguageIsoCodes(inputString){ 656 | let languages = []; 657 | const root = HTMLParser.parse(inputString); 658 | if(root.childNodes.length > 0 && inputString.indexOf('lang') !== -1){ 659 | if(root.childNodes){ 660 | root.childNodes.forEach(htmlElement => { 661 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 662 | languages.push(htmlElement.rawAttributes.lang) 663 | } 664 | }); 665 | } 666 | } 667 | return languages; 668 | } 669 | 670 | function parseHtml(inputString) { 671 | let result = {}; 672 | const root = HTMLParser.parse(inputString); 673 | if(root.childNodes.length > 0 ){ 674 | if (root.childNodes) { 675 | root.childNodes.forEach(htmlElement => { 676 | if(htmlElement.text) { 677 | if (htmlElement.rawAttributes && htmlElement.rawAttributes.hasOwnProperty('lang')) { 678 | result[htmlElement.rawAttributes.lang] = htmlElement.text; 679 | } else { 680 | result[defaultLanguage] = htmlElement.text; 681 | } 682 | } 683 | }); 684 | } 685 | } 686 | else { 687 | result[defaultLanguage] = inputString; 688 | } 689 | return result; 690 | } -------------------------------------------------------------------------------- /tools/jsonld-expander.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 112, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Requirement already satisfied: PyLD in /Users/anisha.keshavan/anaconda3/lib/python3.6/site-packages (1.0.4)\r\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "! pip install PyLD\n", 18 | "from pyld import jsonld\n", 19 | "from copy import deepcopy\n", 20 | "import json" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 103, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "def get_activities(applet_expanded):\n", 30 | " activities = [a['@id'] for a in applet_expanded[0]['https://schema.repronim.org/order'][0]['@list']]\n", 31 | "\n", 32 | " activities_expanded = {a: jsonld.expand(a) for a in activities}\n", 33 | " return activities_expanded\n", 34 | "\n", 35 | "def get_items(activities_expanded):\n", 36 | " items_expanded = {}\n", 37 | " for a in activities_expanded.keys():\n", 38 | " for i in activities_expanded[a][0]['https://schema.repronim.org/order'][0]['@list']:\n", 39 | " items_expanded[i['@id']] = jsonld.expand(i['@id'])\n", 40 | " return items_expanded" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 104, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "def check_for_unexpanded_value_constraints(item_exp):\n", 50 | " vc = item_exp[0]\n", 51 | " if 'https://schema.repronim.org/valueconstraints' in vc.keys():\n", 52 | " vc = vc['https://schema.repronim.org/valueconstraints'][0]\n", 53 | " if isinstance(vc, dict):\n", 54 | " if \"@id\" in vc.keys():\n", 55 | " return True\n", 56 | "\n", 57 | " return False" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 105, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "def expand_value_constraints(original_items_expanded):\n", 67 | " items_expanded = deepcopy(original_items_expanded)\n", 68 | " for item, item_exp in original_items_expanded.items():\n", 69 | " # check if we need to expand valueConstraints\n", 70 | " vc = item_exp[0]\n", 71 | " if 'https://schema.repronim.org/valueconstraints' in vc.keys():\n", 72 | " if check_for_unexpanded_value_constraints(item_exp):\n", 73 | " vc = jsonld.expand(item_exp[0]['https://schema.repronim.org/valueconstraints'][0]['@id'])\n", 74 | " items_expanded[item][0]['https://schema.repronim.org/valueconstraints'][0] = vc\n", 75 | " else:\n", 76 | " multipart_activities = get_activities(item_exp)\n", 77 | " items_expanded.update(multipart_activities)\n", 78 | " return items_expanded" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 106, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "def expand_full(appletURL):\n", 88 | " applet_expanded = jsonld.expand(appletURL)\n", 89 | " \n", 90 | " activities_expanded = get_activities(applet_expanded)\n", 91 | " \n", 92 | " items_expanded = get_items(activities_expanded)\n", 93 | " expItems1 = expand_value_constraints(items_expanded)\n", 94 | " \n", 95 | " # re-expand the items in case any multiparters were added\n", 96 | " expItems2 = expand_value_constraints(expItems1)\n", 97 | " \n", 98 | " return dict(activities = activities_expanded, items = items_expanded, applet = applet_expanded)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 114, 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "appletURL = 'https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activity-sets/voice-pilot/voice_pilot_schema.jsonld'\n", 108 | "\n", 109 | "fully_expanded_voice = expand_full(appletURL)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": 117, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "# json.dumps(fully_expanded_voice)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 109, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "hbn_applet = 'https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activity-sets/ema-hbn/ema-hbn_schema.jsonld'\n", 128 | "fully_expanded_hbn = expand_full(hbn_applet)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 116, 134 | "metadata": { 135 | "scrolled": false 136 | }, 137 | "outputs": [ 138 | { 139 | "data": { 140 | "text/plain": [ 141 | "'{\"activities\": {\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/ema_morning_schema.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/ema_morning_schema\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Activity.jsonld\"], \"http://schema.repronim.org/preamble\": [{\"@language\": \"en\", \"@value\": \"\"}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"Morning Questions\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"https://schema.repronim.org/scoringLogic\": [], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"ema_morning_schema\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"EMA: Morning\"}], \"https://schema.repronim.org/order\": [{\"@list\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/time_in_bed.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/nightmares.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/sleeping_aids.jsonld\"}]}], \"https://schema.repronim.org/shuffle\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"https://schema.repronim.org/visibility\": [{\"@value\": true, \"@index\": \"nightmares\"}, {\"@value\": true, \"@index\": \"sleeping_aids\"}, {\"@value\": true, \"@index\": \"time_in_bed\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/ema_evening_schema.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/ema_evening_schema\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Activity.jsonld\"], \"http://schema.repronim.org/preamble\": [{\"@language\": \"en\", \"@value\": \"\"}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"Evening Questions\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"https://schema.repronim.org/scoringLogic\": [], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"ema_evening_schema\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"EMA: Evening\"}], \"https://schema.repronim.org/order\": [{\"@list\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/stressful_day.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/sources_of_stress.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/good_bad_day.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/enjoyed_day.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/energy.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/health.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/negative_event.jsonld\"}]}], \"https://schema.repronim.org/shuffle\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"https://schema.repronim.org/visibility\": [{\"@value\": true, \"@index\": \"energy\"}, {\"@value\": true, \"@index\": \"enjoyed_day\"}, {\"@value\": true, \"@index\": \"good_bad_day\"}, {\"@value\": true, \"@index\": \"health\"}, {\"@value\": true, \"@index\": \"negative_event\"}, {\"@language\": \"en\", \"@value\": \"stressful_day\", \"@index\": \"sources_of_stress\"}, {\"@value\": true, \"@index\": \"stressful_day\"}]}]}, \"items\": {\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/time_in_bed.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/time_in_bed\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Mark the hours that your child was in bed\"}], \"https://schema.repronim.org/valueconstraints\": [{}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"time spent in bed\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"time_in_bed\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Time in bed\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"timeRange\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/nightmares.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/nightmares\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Did your child have any nightmares or night terrors last night?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#anyURI\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F634.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"No\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F62B.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Yes\"}], \"http://schema.org/value\": [{\"@value\": 1}]}]}], \"http://schema.repronim.org/multipleChoice\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"http://schema.org/maxValue\": [{\"@value\": 1}], \"http://schema.org/minValue\": [{\"@value\": 0}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"whether or not your child experience nightmares or night terrors\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"nightmares\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Nightmares\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"radio\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/sleeping_aids.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/items/sleeping_aids\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Did your child take sleeping pills or anything else to help their sleep last night?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#anyURI\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F6CF.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"No\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F48A.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Yes\"}], \"http://schema.org/value\": [{\"@value\": 1}]}]}], \"http://schema.repronim.org/multipleChoice\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"http://schema.org/maxValue\": [{\"@value\": 1}], \"http://schema.org/minValue\": [{\"@value\": 0}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"whether or not your child took sleeping aids to fall asleep last night\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"sleeping_aids\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Sleeping Aids\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"radio\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/stressful_day.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/stressful_day\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Do you think your child had a stressful day today?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#anyURI\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F60A.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"No\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F630.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Yes\"}], \"http://schema.org/value\": [{\"@value\": 1}]}]}], \"http://schema.repronim.org/multipleChoice\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"http://schema.org/maxValue\": [{\"@value\": 1}], \"http://schema.org/minValue\": [{\"@value\": 0}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"whether or not your child took sleeping aids to fall asleep last night\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"stressful_day\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Stress\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"radio\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/sources_of_stress.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/sources_of_stress\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Choose the source(s) of your child\\'s stress today:\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#anyURI\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"@type\": [\"http://schema.org/option\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F915.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Didn\\'t feel well\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"@type\": [\"http://schema.org/option\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F469-200D-1F3EB.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Classroom learning\"}], \"http://schema.org/value\": [{\"@value\": 1}]}, {\"@type\": [\"http://schema.org/option\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F4DD.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"A test or quiz at school\"}], \"http://schema.org/value\": [{\"@value\": 2}]}, {\"@type\": [\"http://schema.org/option\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F47F.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Bullying\"}], \"http://schema.org/value\": [{\"@value\": 3}]}, {\"@type\": [\"http://schema.org/option\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/E246.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Online relationships\"}], \"http://schema.org/value\": [{\"@value\": 4}]}, {\"@type\": [\"http://schema.org/option\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/2764.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Death of a loved one\"}], \"http://schema.org/value\": [{\"@value\": 5}]}]}], \"http://schema.repronim.org/multipleChoice\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": true}], \"http://schema.org/maxValue\": [{\"@value\": 2}], \"http://schema.org/minValue\": [{\"@value\": 0}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"your child\\'s sources of stress\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"source_of_stress\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Sources of Stress\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"radio\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/good_bad_day.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/good_bad_day\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Did your child have a good day or bad day today?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#anyURI\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F44E.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Bad Day\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F44D.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Good Day\"}], \"http://schema.org/value\": [{\"@value\": 1}]}]}], \"http://schema.repronim.org/multipleChoice\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"http://schema.org/maxValue\": [{\"@value\": 1}], \"http://schema.org/minValue\": [{\"@value\": 0}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"did you child have a good or bad day\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"good_bad_day\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Good or Bad day\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"radio\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/enjoyed_day.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/enjoyed_day\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Did your child seem like they enjoyed the day?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#integer\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F621.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"0\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"1\"}], \"http://schema.org/value\": [{\"@value\": 1}]}, {\"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"2\"}], \"http://schema.org/value\": [{\"@value\": 2}]}, {\"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"3\"}], \"http://schema.org/value\": [{\"@value\": 3}]}, {\"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"4\"}], \"http://schema.org/value\": [{\"@value\": 4}]}, {\"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F601.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"5\"}], \"http://schema.org/value\": [{\"@value\": 5}]}]}], \"http://schema.repronim.org/requiredValue\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"http://schema.org/maxValue\": [{\"@language\": \"en\", \"@value\": \"Really enjoying things\"}], \"http://schema.org/minValue\": [{\"@language\": \"en\", \"@value\": \"No enjoyment or pleasure\"}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"whether or not your child took sleeping aids to fall asleep last night\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"enjoyed_day\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Enjoyed the day\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"slider\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/energy.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/energy\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"How tired vs energetic did you child seem today?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#integer\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F634.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"1\"}], \"http://schema.org/value\": [{\"@value\": 1}]}, {\"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"2\"}], \"http://schema.org/value\": [{\"@value\": 2}]}, {\"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"3\"}], \"http://schema.org/value\": [{\"@value\": 3}]}, {\"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"4\"}], \"http://schema.org/value\": [{\"@value\": 4}]}, {\"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F606.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"5\"}], \"http://schema.org/value\": [{\"@value\": 5}]}]}], \"http://schema.repronim.org/requiredValue\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"http://schema.org/maxValue\": [{\"@language\": \"en\", \"@value\": \"high energy\"}], \"http://schema.org/minValue\": [{\"@language\": \"en\", \"@value\": \"very tired\"}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"how energetic your child was today\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"energy\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Energy level\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"slider\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/health.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/health\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Was your child in good health today?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#anyURI\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/1F915.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"No\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/image\": [{\"@language\": \"en\", \"@value\": \"https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/svg/263A.svg?sanitize=true\"}], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Yes\"}], \"http://schema.org/value\": [{\"@value\": 1}]}]}], \"http://schema.repronim.org/multipleChoice\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"http://schema.org/maxValue\": [{\"@value\": 1}], \"http://schema.org/minValue\": [{\"@value\": 0}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"was your child in good health today\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"health\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"General Health\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"radio\"}]}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/negative_event.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/items/sleeping_aids\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/Field.jsonld\"], \"http://schema.org/question\": [{\"@language\": \"en\", \"@value\": \"Please think of ONE event that may have affected your child the most today (positively or negatively), no matter how slightly. In what context did the event occur?\"}], \"https://schema.repronim.org/valueconstraints\": [{\"@type\": [\"http://www.w3.org/2001/XMLSchema#anyURI\"], \"http://schema.org/itemListElement\": [{\"@list\": [{\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"No\"}], \"http://schema.org/value\": [{\"@value\": 0}]}, {\"@type\": [\"http://schema.org/Boolean\"], \"http://schema.org/name\": [{\"@language\": \"en\", \"@value\": \"Yes\"}], \"http://schema.org/value\": [{\"@value\": 1}]}]}], \"http://schema.repronim.org/multipleChoice\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": true}], \"http://schema.org/maxValue\": [{\"@value\": 1}], \"http://schema.org/minValue\": [{\"@value\": 0}]}], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"whether or not your child took sleeping aids to fall asleep last night\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"sleeping_aids\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Sleeping Aids\"}], \"https://schema.repronim.org/inputType\": [{\"@type\": \"http://www.w3.org/2001/XMLSchema#string\", \"@value\": \"radio\"}]}]}, \"applet\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activity-sets/ema-hbn/ema-hbn_schema\", \"@type\": [\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/schemas/ActivitySet.jsonld\"], \"http://schema.org/description\": [{\"@language\": \"en\", \"@value\": \"Daily questions about your child\\'s physical and mental health\"}], \"http://schema.org/schemaVersion\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://schema.org/version\": [{\"@language\": \"en\", \"@value\": \"0.0.1\"}], \"http://www.w3.org/2004/02/skos/core#altLabel\": [{\"@language\": \"en\", \"@value\": \"ema-hbn\"}], \"http://www.w3.org/2004/02/skos/core#prefLabel\": [{\"@language\": \"en\", \"@value\": \"Healthy Brain Network: EMA\"}], \"https://schema.repronim.org/activity_display_name\": [{\"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/ema_evening_schema.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activity-sets/ema-hbn/Evening\"}], \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/ema_morning_schema.jsonld\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activity-sets/ema-hbn/Morning\"}]}], \"https://schema.repronim.org/order\": [{\"@list\": [{\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNMorning/ema_morning_schema.jsonld\"}, {\"@id\": \"https://raw.githubusercontent.com/ReproNim/schema-standardization/master/activities/EmaHBNEvening/ema_evening_schema.jsonld\"}]}], \"https://schema.repronim.org/shuffle\": [{\"@type\": \"http://schema.org/Boolean\", \"@value\": false}], \"https://schema.repronim.org/visibility\": [{\"@value\": true, \"@index\": \"ema_evening\"}, {\"@value\": true, \"@index\": \"ema_morning\"}]}]}'" 142 | ] 143 | }, 144 | "execution_count": 116, 145 | "metadata": {}, 146 | "output_type": "execute_result" 147 | } 148 | ], 149 | "source": [ 150 | "json.dumps(fully_expanded_hbn)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [] 159 | } 160 | ], 161 | "metadata": { 162 | "kernelspec": { 163 | "display_name": "Python 3", 164 | "language": "python", 165 | "name": "python3" 166 | }, 167 | "language_info": { 168 | "codemirror_mode": { 169 | "name": "ipython", 170 | "version": 3 171 | }, 172 | "file_extension": ".py", 173 | "mimetype": "text/x-python", 174 | "name": "python", 175 | "nbconvert_exporter": "python", 176 | "pygments_lexer": "ipython3", 177 | "version": "3.6.8" 178 | } 179 | }, 180 | "nbformat": 4, 181 | "nbformat_minor": 2 182 | } 183 | --------------------------------------------------------------------------------