diff --git a/.vscode/launch.json b/.vscode/launch.json index 64570714..bc41173b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -39,12 +39,12 @@ "internalConsoleOptions": "neverOpen" }, { + "name": "Launch Chromium", "type": "chrome", "request": "launch", - "name": "Launch Chromium Ubuntu (http://localhost:8080)", - "url": "http://localhost:8080", + "cwd": "${workspaceFolder}", "webRoot": "${workspaceFolder}", - "runtimeExecutable": "/snap/bin/chromium" + "url": "http://localhost:8080" } ] } \ No newline at end of file diff --git a/i18n/catroid_strings/values-en/strings.xml b/i18n/catroid_strings/values-en/strings.xml index 8caf0359..adf38b92 100644 --- a/i18n/catroid_strings/values-en/strings.xml +++ b/i18n/catroid_strings/values-en/strings.xml @@ -1911,6 +1911,8 @@ needs read and write access to it. You can always change permissions through you gamepad down pressed gamepad left pressed gamepad right pressed + stage width + stage height phiro front left sensor phiro front right sensor phiro side left sensor @@ -2176,7 +2178,7 @@ needs read and write access to it. You can always change permissions through you Delete? Deleting a variable or list that is still in use can cause - errors. + errors. You can\'t undo this! Your project gets uploaded to the app\’s sharing site where others @@ -2268,10 +2270,4 @@ needs read and write access to it. You can always change permissions through you Undo sort Sort checkbox - - Cannot undo renaming of variable %1$s. It - appears another variable has the same name. - You can no longer restore variable %1$s - - diff --git a/i18n/strings_to_json_mapping.json b/i18n/strings_to_json_mapping.json index f0facffb..ef779237 100644 --- a/i18n/strings_to_json_mapping.json +++ b/i18n/strings_to_json_mapping.json @@ -299,9 +299,9 @@ "LEGOEV3_MOTORSTOP": "${ev3_motor_stop} %1%2", "LEGOEV3_PLAYTONE": "${ev3_play_tone} ${ev3_tone_duration_for} %1%2 ${second_plural.one} ${nxt_tone_frequency} %3%4 ${nxt_tone_hundred_hz} ${ev3_tone_volume} %5%6 ${ev3_tone_percent}", "LEGOEV3_SETLED": "${ev3_set_led_status} %1%2", - "MOTOR_LEFT": "${phiro_motor_left}", - "MOTOR_RIGHT": "${phiro_motor_right}", - "MOTOR_BOTH": "${phiro_motor_both}", + "PHIRO_MOTOR_LEFT": "${phiro_motor_left}", + "PHIRO_MOTOR_RIGHT": "${phiro_motor_right}", + "PHIRO_MOTOR_BOTH": "${phiro_motor_both}", "DRONE_TAKEOFFLAND": "${brick_drone_takeoff_land}", "DRONE_EMERGENCY": "${brick_drone_emergency}", "DRONE_MOVEUP": "${brick_drone_move_up} %1%2 ${second_plural.one} ${brick_drone_with.} %3%4 ${ev3_tone_percent} ${brick_drone_power}", @@ -444,6 +444,12 @@ "EV3_SENSOR_2": "${formula_editor_sensor_lego_ev3_2}", "EV3_SENSOR_3": "${formula_editor_sensor_lego_ev3_3}", "EV3_SENSOR_4": "${formula_editor_sensor_lego_ev3_4}", + "EV3_MOTOR_A": "${ev3_motor_a}", + "EV3_MOTOR_B": "${ev3_motor_b}", + "EV3_MOTOR_C": "${ev3_motor_c}", + "EV3_MOTOR_D": "${ev3_motor_d}", + "EV3_MOTOR_MOTOR_B_C": "${ev3_motor_b_anc_d}", + "EV3_MOTOR_ALL_MOTORS": "${ev3_motor_all}", "COLOR_AT_XY": "${formula_editor_sensor_color_at_x_y}", "COLOR_TOUCHES_COLOR": "${formula_editor_function_color_touches_color}", "COLOR_EQUALS_COLOR": "${formula_editor_sensor_color_equals_color}", @@ -493,14 +499,14 @@ "ALIGNMENTS_0": "${brick_show_variable_aligned_left}", "ALIGNMENTS_1": "${brick_show_variable_aligned_centered}", "ALIGNMENTS_2": "${brick_show_variable_aligned_right}", - "SPINNER_0": "${brick_stop_this_script}", - "SPINNER_1": "${brick_stop_all_scripts}", - "SPINNER_2": "${brick_stop_other_scripts}", + "STOP_SCRIPT_0": "${brick_stop_this_script}", + "STOP_SCRIPT_1": "${brick_stop_all_scripts}", + "STOP_SCRIPT_2": "${brick_stop_other_scripts}", "SPEECH_RECOGNITION_LANGUAGE": "${preference_title_ai_speech_recognition}", - "CAMSPINNER_0": "${video_brick_camera_off}", - "CAMSPINNER_1": "${video_brick_camera_on}", - "CAMCHOOSESPINNER_0": "${choose_camera_back}", - "CAMCHOOSESPINNER_1": "${choose_camera_front}", + "CAMSPINNER_FALSE": "${video_brick_camera_off}", + "CAMSPINNER_TRUE": "${video_brick_camera_on}", + "CAMCHOOSESPINNER_FALSE": "${choose_camera_back}", + "CAMCHOOSESPINNER_TRUE": "${choose_camera_front}", "FLASHSPINNER_0": "${brick_flash_off}", "FLASHSPINNER_1": "${brick_flash_on}", "FADESPINNER_0": "${particle_effects_fade_in}", @@ -564,27 +570,27 @@ "CLOSE": "${close}", "SHOW_VARIABLE": "${brick_show_variable}", "SWITCH_TO_1D": "${switch_to_1d}", - "GO_TO_TOUCH_POSITION": "${brick_go_to_touch_position}", - "GO_TO_RANDOM_POSITION": "${brick_go_to_random_position}", - "MOTOR_A": "${nxt_motor_a}", - "MOTOR_B": "${nxt_motor_b}", - "MOTOR_C": "${nxt_motor_c}", - "MOTOR_B_C": "${nxt_motor_b_and_c}", - "MOTOR_D": "${ev3_motor_d}", - "LED_OFF": "${ev3_led_status_off}", - "LED_GREEN": "${ev3_led_status_green}", - "LED_RED": "${ev3_led_status_red}", - "LED_ORANGE": "${ev3_led_status_orange}", - "LED_GREEN_FLASHING": "${ev3_led_status_green_flashing}", - "LED_RED_FLASHING": "${ev3_led_status_red_flashing}", - "LED_ORANGE_FLASHING": "${ev3_led_status_orange_flashing}", - "LED_GREEN_PULSE": "${ev3_led_status_green_pulse}", - "LED_RED_PULSE": "${ev3_led_status_red_pulse}", - "LED_ORANGE_PULSE": "${ev3_led_status_orange_pulse}", - "DEFAULT": "${sound_default}", - "MONSTER": "${sound_monster}", - "INSECT": "${sound_insect}", - "ROBOT": "${sound_robot}", + "GO_TO_POSITION_80": "${brick_go_to_touch_position}", + "GO_TO_POSITION_81": "${brick_go_to_random_position}", + "NXT_MOTOR_A": "${nxt_motor_a}", + "NXT_MOTOR_B": "${nxt_motor_b}", + "NXT_MOTOR_C": "${nxt_motor_c}", + "NXT_MOTOR_B_C": "${nxt_motor_b_and_c}", + "NXT_ALL_MOTORS": "${nxt_motor_all}", + "EV3_LED_OFF": "${ev3_led_status_off}", + "EV3_LED_GREEN": "${ev3_led_status_green}", + "EV3_LED_RED": "${ev3_led_status_red}", + "EV3_LED_ORANGE": "${ev3_led_status_orange}", + "EV3_LED_GREEN_FLASHING": "${ev3_led_status_green_flashing}", + "EV3_LED_RED_FLASHING": "${ev3_led_status_red_flashing}", + "EV3_LED_ORANGE_FLASHING": "${ev3_led_status_orange_flashing}", + "EV3_LED_GREEN_PULSE": "${ev3_led_status_green_pulse}", + "EV3_LED_RED_PULSE": "${ev3_led_status_red_pulse}", + "EV3_LED_ORANGE_PULSE": "${ev3_led_status_orange_pulse}", + "JUMPING_SUMO_SOUND_DEFAULT": "${sound_default}", + "JUMPING_SUMO_SOUND_MONSTER": "${sound_monster}", + "JUMPING_SUMO_SOUND_INSECT": "${sound_insect}", + "JUMPING_SUMO_SOUND_ROBOT": "${sound_robot}", "SECONDS_PLURAL": "${second_plural.other}", "STEPS_PLURAL": "${brick_move_n_step_plural.other}", "LAYERS_PLURAL": "${brick_go_back_layer_plural.other}", @@ -610,5 +616,45 @@ "PHIRO_TONE_FA": "${phiro_tone_fa}", "PHIRO_TONE_SO": "${phiro_tone_so}", "PHIRO_TONE_LA": "${phiro_tone_la}", - "PHIRO_TONE_TI": "${phiro_tone_ti}" + "PHIRO_TONE_TI": "${phiro_tone_ti}", + "INSTRUMENT_ELECTRIC_PIANO": "${electric_piano}", + "INSTRUMENT_PIANO": "${piano}", + "INSTRUMENT_CELLO": "${cello}", + "INSTRUMENT_FLUTE": "${flute}", + "INSTRUMENT_VIBRAPHONE": "${vibraphone}", + "INSTRUMENT_ORGAN": "${organ}", + "INSTRUMENT_GUITAR": "${guitar}", + "INSTRUMENT_ELECTRIC_GUITAR": "${electric_guitar}", + "INSTRUMENT_BASS": "${bass}", + "INSTRUMENT_PIZZICATO": "${pizzicato}", + "INSTRUMENT_SYNTH_PAD": "${synth_pad}", + "INSTRUMENT_CHOIR": "${choir}", + "INSTRUMENT_SYNTH_LEAD": "${synth_lead}", + "INSTRUMENT_WOODEN_FLUTE": "${wooden_flute}", + "INSTRUMENT_TROMBONE": "${trombone}", + "INSTRUMENT_SAXOPHONE": "${saxophone}", + "INSTRUMENT_BASSOON": "${bassoon}", + "INSTRUMENT_CLARINET": "${clarinet}", + "INSTRUMENT_MUSIC_BOX": "${music_box}", + "INSTRUMENT_STEEL_DRUM": "${steel_drum}", + "INSTRUMENT_MARIMBA": "${marimba}", + "DRUM_SNARE_DRUM": "${snare_drum}", + "DRUM_BASS_DRUM": "${bass_drum}", + "DRUM_SIDE_STICK": "${side_stick}", + "DRUM_CRASH_CYMBAL": "${crash_cymbal}", + "DRUM_OPEN_HI_HAT": "${open_hi_hat}", + "DRUM_CLOSED_HI_HAT": "${closed_hi_hat}", + "DRUM_TAMBOURINE": "${tambourine}", + "DRUM_HAND_CLAP": "${hand_clap}", + "DRUM_CLAVES": "${claves}", + "DRUM_WOOD_BLOCK": "${wood_block}", + "DRUM_COWBELL": "${cowbell}", + "DRUM_TRIANGLE": "${triangle}", + "DRUM_BONGO": "${bongo}", + "DRUM_CONGA": "${conga}", + "DRUM_CABASA": "${cabasa}", + "DRUM_GUIRO": "${guiro}", + "DRUM_VIBRASLAP": "${vibraslap}", + "DRUM_OPEN_CUICA": "${open_cuica}", + "WHEN_BOUNCE_OFF_ANYTHING": "${collision_with_anything}" } \ No newline at end of file diff --git a/src/common/js/parser/formula.js b/src/common/js/parser/formula.js deleted file mode 100644 index fd7887c1..00000000 --- a/src/common/js/parser/formula.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Catblocks formular class for parsing catroid programs - */ - -export default class Formula { - constructor() { - this.value = ''; - this.operator = ''; - this.left = null; - this.right = null; - this.mid = null; - } - - setLeft(leftBlock) { - if (this.left === null) { - this.left = leftBlock; - } else { - this.left.setLeft(leftBlock); - } - } - - setRight(rightBlock) { - if (this.right === null) { - this.right = rightBlock; - } else { - this.right.setRight(rightBlock); - } - } - - setMid(midBlock) { - if (this.mid === null) { - this.mid = midBlock; - } else { - this.mid.setMid(midBlock); - } - } - - static getAllLayouts() { - return { - BRACKET: '(%l%r)', - USER_LIST: '*%v*', - STRING: "'%v'", - USER_VARIABLE: '"%v"', - SIN: '%v(%l)', - COS: '%v(%l)', - TAN: '%v(%l)', - LN: '%v(%l)', - LOG: '%v(%l)', - ABS: '%v(%l)', - ROUND: '%v(%l)', - ARCSIN: '%v(%l)', - ARCCOS: '%v(%l)', - ARCTAN: '%v(%l)', - FLOOR: '%v(%l)', - CEIL: '%v(%l)', - EXP: '%v(%l)', - SQRT: '%v(%l)', - MULTI_FINGER_X: '%v(%l)', - MULTI_FINGER_Y: '%v(%l)', - MULTI_FINGER_TOUCHED: '%v(%l)', - TEXT_BLOCK_X: '%v(%l)', - TEXT_BLOCK_Y: '%v(%l)', - TEXT_BLOCK_SIZE: '%v(%l)', - TEXT_BLOCK_LANGUAGE_FROM_CAMERA: '%v(%l)', - ARDUINOANALOG: '%v(%l)', - ARDUINODIGITAL: '%v(%l)', - ARCTAN2: '%v(%l, %r)', - POWER: '%v(%l, %r)', - RASPIDIGITAL: '%v(%l)', - MOD: '%v(%l, %r)', - RAND: '%v(%l, %r)', - MAX: '%v(%l, %r)', - MIN: '%v(%l, %r)', - IF_THEN_ELSE: '%v(%l,%r,%m)', - LENGTH: '%v(%l)', - LETTER: '%v(%l, %r)', - JOIN: '%v(%l, %r)', - JOIN3: '%v(%l,%r,%m)', - FLATTEN: '%v(%l)', - INDEX_OF_ITEM: '%v(%l, %r)', - INDEX_CURRENT_TOUCH: '%v(%l)', - COLOR_AT_XY: '%v(%l, %r)', - COLOR_TOUCHES_COLOR: '%v(%l, %r)', - COLOR_EQUALS_COLOR: '%v(%l, %r)', - COLLIDES_WITH_COLOR: '%v(%l)', - REGEX: '%v(%l, %r)', - CONTAINS: '%v(%l, %r)', - NUMBER_OF_ITEMS: '%v(%l)', - LIST_ITEM: '%v(%l, %r)', - DEFAULT: '%l %v %r' - }; - } - - static getOpLayout(op) { - if (op) { - const layout = Formula.getAllLayouts()[op]; - if (layout) { - return layout; - } - } - return Formula.getAllLayouts()['DEFAULT']; - } - - static packValue(layout, key, value) { - if (['%v', '%l', '%r', '%m'].includes(key)) { - if (value.length > 0) { - const result = value.replace(/(\.[0-9]*[1-9])0+$|\.0*$/, '$1'); - return layout.replace(key, `${result}`); - } - return layout.replace(key, ''); - } - return layout; - } - - static packLayout(op, value, left, right, mid) { - let layout = Formula.getOpLayout(op); - layout = Formula.packValue(layout, '%v', value); - layout = Formula.packValue(layout, '%l', left); - layout = Formula.packValue(layout, '%r', right); - layout = Formula.packValue(layout, '%m', mid); - return layout; - } - - static stringify(f) { - const left = (() => { - if (f.left) { - return Formula.stringify(f.left); - } - return ''; - })(); - const right = (() => { - if (f.right) { - return Formula.stringify(f.right); - } - return ''; - })(); - const mid = (() => { - if (f.mid) { - return Formula.stringify(f.mid); - } - return ''; - })(); - - const nodeValue = Formula.packLayout(f.operator, f.value, left, right, mid); - return nodeValue; - } -} diff --git a/src/common/js/parser/parser.js b/src/common/js/parser/parser.js deleted file mode 100644 index 63ebf196..00000000 --- a/src/common/js/parser/parser.js +++ /dev/null @@ -1,1106 +0,0 @@ -import { CatBlocksMsgs } from '../../../library/ts/i18n/CatBlocksMsgs'; -import Formula from './formula'; - -class Scene { - constructor(name) { - this.name = name; - this.objectList = []; - } -} - -class Object { - constructor(name) { - this.name = name; - this.lookList = []; - this.soundList = []; - this.scriptList = []; - this.userBricks = null; - } -} - -class File { - constructor(name, fileName) { - this.name = name; - this.fileName = fileName; - } -} - -class Script { - constructor(name, id, posX, posY, commentedOut) { - this.name = name; - this.brickList = []; - this.posX = posX; - this.posY = posY; - this.id = id; - this.commentedOut = commentedOut; - this.formValues = new Map(); - } -} - -class Brick { - constructor(name, id) { - this.name = name; - if (id) { - this.id = id; - } - this.loopOrIfBrickList = []; - this.elseBrickList = []; - this.formValues = new Map(); - this.colorVariation = 0; - this.userBrickId = undefined; - this.commentedOut = false; - this.endBrickList = []; - } -} - -class UserBrickDefinition { - constructor(id) { - this.id = id; - this.inputTypes = []; - this.msg = ''; - } - - getArgs(fieldNameEqualsContent) { - const args = []; - for (let i = 0; i < this.inputTypes.length; ++i) { - if (this.inputTypes[i].type.toUpperCase() == 'INPUT') { - args.push({ - type: 'field_catblockstext', - name: this.inputTypes[i].varName, - text: fieldNameEqualsContent ? this.inputTypes[i].varName : 'unset' - }); - args.push({ - type: 'field_image', - name: `${this.inputTypes[i].varName}_INFO`, - src: `${document.location.pathname}media/info_icon.svg`, - height: 24, - width: 24, - alt: '(i)', - flip_rtl: true - }); - } - } - return args; - } - - getJsonDefinition() { - const args = this.getArgs(false); - return { - message0: this.msg, - args0: args, - args2: args, - category: 'user', - colour: '#3556a2', - extensions: ['shapeBrick'] - }; - } - - getFormValueMapForDefinitionBrick() { - const args = new Map(); - for (let i = 0; i < this.inputTypes.length; ++i) { - if (this.inputTypes[i].type.toUpperCase() == 'INPUT') { - args.set(this.inputTypes[i].varName, this.inputTypes[i].varName); - } - } - return args; - } - - getDefinitionJsonDefinition() { - const args = this.getArgs(true); - return { - message0: this.msg, - args0: args, - category: 'user', - colour: '#3556a2', - previousStatement: 'userdefinedtemplate', - nextStatement: 'userdefinedtemplate', - formValueMap: this.getFormValueMapForDefinitionBrick() - }; - } -} - -const sceneList = []; -let xmlDoc = undefined; -const supportedAppVersion = 0.9994; - -// global log enable switch -const DEBUG = false; - -/** - * Catblocks debug function - * @param {*} msg - * @param {*} debug - */ -const catLog = (msg, debug = DEBUG) => { - if (debug) { - console.log(msg); - } -}; - -/** - * Check if current catroid code version is supported - * @param {XMLDocument} program to validate - * @return {boolean} if supported or not - */ -function isSupported(program) { - const appVersion = program.getElementsByTagName('catrobatLanguageVersion'); - if (appVersion === undefined || appVersion.length < 1) { - console.warn('Unsupported program version found, please upgrade programm to newer version via reupload.'); - return false; - } - if (appVersion[0].innerHTML < supportedAppVersion) { - console.warn('Unsupported program version found, please upgrade programm to newer version via reupload.'); - return false; - } - return true; -} - -/** - * Initialize parser for new conversion - * Clean old parsed values and define xmlDoc for xPath - * @param {XMLDocument} xml - */ -function initParser(xml) { - xmlDoc = xml; - sceneList.length = 0; -} - -/** - * Get the xml program as JSON - * @param {XMLDocument} xml catroid program xml - * @returns {Object} parsed program - */ -function getCatroidProgramObject(xml) { - const scenes = xml.getElementsByTagName('scenes')[0].children; - for (let i = 0; i < scenes.length; i++) { - sceneList.push(parseScenes(scenes[i])); - } - const name = xml.getElementsByTagName('header')[0].getElementsByTagName('programName')[0].innerHTML; - return { scenes: sceneList, programName: name }; -} -/** - * Flat/dereference xml nodes - * @param {*} node node - * @param {*} xml XMLDocument - */ -function flatReference(node, xml = xmlDoc) { - const refPath = node.getAttribute('reference'); - if (refPath) { - return xml.evaluate(refPath, node, null, XPathResult.ANY_TYPE, null).iterateNext(); - } - return node; -} - -/** - * Escape not allowed characters in names - * @param {string} name to escape - * @returns {string} proper value - */ -function escapeName(name) { - return (name || '').replace(/[&]/, ''); -} - -function parseScenes(scene) { - catLog(scene); - - const name = escapeName(scene.getElementsByTagName('name')[0].childNodes[0].nodeValue); - const currentScene = new Scene(name); - const objectList = scene.getElementsByTagName('objectList')[0].children; - for (let i = 0; i < objectList.length; i++) { - currentScene.objectList.push(parseObjects(objectList[i])); - } - return currentScene; -} - -function parseObjects(object) { - object = flatReference(object); - catLog(object); - - const name = escapeName(object.getAttribute('name')); - if (name !== null) { - const currentObject = new Object(name); - const lookList = object.getElementsByTagName('lookList')[0].children; - const soundList = object.getElementsByTagName('soundList')[0].children; - const scriptList = object.getElementsByTagName('scriptList')[0].children; - - const userDefinedBrickList = object.getElementsByTagName('userDefinedBrickList'); - if (userDefinedBrickList && userDefinedBrickList[0] && userDefinedBrickList[0].children) { - const userBrickDefinitions = parseUserBrickDefinitions(userDefinedBrickList[0].children); - currentObject.userBricks = userBrickDefinitions; - } - - for (let i = 0; i < lookList.length; i++) { - let name = lookList[i].getAttribute('name'); - if (name == null) { - const xml = lookList[i].getElementsByTagName('name'); - if (xml.length > 0 && xml[0] !== undefined) { - name = xml[0].textContent; - } - } - - let fileName = lookList[i].getAttribute('fileName'); - if (fileName == null) { - const xml = lookList[i].getElementsByTagName('fileName'); - if (xml.length > 0 && xml[0] !== undefined) { - fileName = xml[0].textContent; - } - } - - const file = new File(name, fileName); - currentObject.lookList.push(file); - } - for (let i = 0; i < soundList.length; i++) { - let name = soundList[i].getAttribute('name'); - if (name == null) { - const xml = soundList[i].getElementsByTagName('name'); - if (xml.length > 0 && xml[0] !== undefined) { - name = xml[0].textContent; - } - } - - let fileName = soundList[i].getAttribute('fileName'); - if (fileName == null) { - const xml = soundList[i].getElementsByTagName('fileName'); - if (xml.length > 0 && xml[0] !== undefined) { - fileName = xml[0].textContent; - } - } - - const file = new File(name, fileName); - currentObject.soundList.push(file); - } - for (let i = 0; i < scriptList.length; i++) { - currentObject.scriptList.push(parseScripts(scriptList[i])); - } - return currentObject; - } -} - -function parseUserBrickDefinitions(userBricks) { - const userDefinedBrickDefinitions = []; - - for (let i = 0; i < userBricks.length; ++i) { - const brickDefinition = userBricks[i]; - if (!brickDefinition) { - continue; - } - - // - let msg = ''; - const inputs = []; - const brickId = brickDefinition.getElementsByTagName('userDefinedBrickID')[0].innerHTML; - if (!brickId) { - continue; - } - - const brickDataDefNode = brickDefinition.getElementsByTagName('userDefinedBrickDataList')[0]; - if (!brickDataDefNode) { - continue; - } - const brickDataDefs = flatReference(brickDataDefNode); - - let inputCounter = 1; - // - for (let j = 0; j < brickDataDefs.children.length; ++j) { - const dataDef = brickDataDefs.children[j]; - if (dataDef.nodeName == 'userDefinedBrickLabel') { - msg += dataDef.getElementsByTagName('label')[0].innerHTML + ' '; - } else if (dataDef.nodeName == 'userDefinedBrickInput') { - msg += `%${inputCounter}%${inputCounter + 1} `; - - const inputTag = dataDef.getElementsByTagName('input')[0]; - let varName; - if (inputTag.hasAttribute('reference')) { - const inputRef = inputTag.getAttribute('reference'); - varName = xmlDoc.evaluate(inputRef, inputTag).iterateNext().getElementsByTagName('input')[0].innerHTML; - } else { - varName = inputTag.getElementsByTagName('input')[0].innerHTML; - } - - inputs.push({ - type: dataDef.getElementsByTagName('type')[0].innerHTML, - varName: varName - }); - inputCounter += 2; - } else { - throw `Unknown user brick data definition: ${dataDef.nodeName}`; - } - } - msg = msg.trimRight(); - - const userBrick = new UserBrickDefinition(brickId); - userDefinedBrickDefinitions.push(userBrick); - userBrick.msg = msg; - userBrick.inputTypes = inputs; - } - - return userDefinedBrickDefinitions; -} - -function parseScripts(script) { - catLog(script); - - const name = escapeName(script.getAttribute('type')); - let posX = undefined; - let posY = undefined; - if (script.hasAttribute('posX') && script.hasAttribute('posY')) { - posX = script.getAttribute('posX'); - posY = script.getAttribute('posY'); - } - const scriptIdTag = script.getElementsByTagName('scriptId'); - let scriptId = undefined; - if (scriptIdTag.length > 0) { - scriptId = scriptIdTag[0].innerHTML; - } - - let commentedOut = false; - const commentedOutTag = script.getElementsByTagName('commentedOut'); - if (commentedOutTag.length > 0) { - commentedOut = commentedOutTag[0].innerHTML == 'true'; - } - - const currentScript = new Script(name, scriptId, posX, posY, commentedOut); - - const actionTags = script.getElementsByTagName('action'); - if (actionTags && actionTags.length > 0) { - currentScript.formValues.set('ACTION', actionTags[0].textContent); - } - - const brickList = script.getElementsByTagName('brickList')[0].children; - for (let i = 0; i < script.childNodes.length; i++) { - checkUsage(script.childNodes[i], currentScript); - } - - let positionInScriptBrickList = 0; - for (let i = 0; i < brickList.length; i++) { - if (brickList[i].attributes[0].value === 'RepeatBrick' && checkIfNewProgram('RepeatBrick', brickList)) { - const loopFinished = fillLoopControlBrick( - brickList, - currentScript, - 'RepeatBrick', - i, - positionInScriptBrickList, - null, - true - ); - i = loopFinished + 1; - } else if ( - brickList[i].attributes[0].value === 'IfThenLogicBeginBrick' && - checkIfNewProgram('IfThenLogicBeginBrick', brickList) - ) { - const ifFinished = fillLoopControlBrick( - brickList, - currentScript, - 'IfThenLogicBeginBrick', - i, - positionInScriptBrickList, - null, - true - ); - i = ifFinished + 1; - } else if ( - brickList[i].attributes[0].value === 'IfLogicBeginBrick' && - checkIfNewProgram('IfLogicBeginBrick', brickList) - ) { - const ifFinished = fillLoopControlBrick( - brickList, - currentScript, - 'IfLogicBeginBrick', - i, - positionInScriptBrickList, - null, - true - ); - i = ifFinished + 1; - } else { - currentScript.brickList.push(parseBrick(brickList[i])); - positionInScriptBrickList++; - } - } - return currentScript; -} - -function checkIfNewProgram(currentBrick, brickList) { - let endBrick; - if (currentBrick === 'RepeatBrick') { - endBrick = 'LoopEndBrick'; - } else if (currentBrick === 'IfLogicBeginBrick') { - endBrick = 'IfLogicEndBrick'; - } else if (currentBrick === 'IfThenLogicBeginBrick') { - endBrick = 'IfThenLogicEndBrick'; - } - - for (let i = 0; i < brickList.length; i++) { - if (brickList[i].attributes[0].value === endBrick) { - return true; - } - } - return false; -} - -function fillLoopControlBrick( - brickList, - currentScript, - currentBrick, - counter, - positionInScriptBrickList, - currentListToFill = null, - firstCall = false -) { - let i = counter; - let endBrick; - let elseBrick; - if (currentBrick === 'RepeatBrick') { - endBrick = 'LoopEndBrick'; - elseBrick = null; - } else if (currentBrick === 'IfLogicBeginBrick') { - endBrick = 'IfLogicEndBrick'; - elseBrick = 'IfLogicElseBrick'; - } else if (currentBrick === 'IfThenLogicBeginBrick') { - endBrick = 'IfThenLogicEndBrick'; - elseBrick = 'IfThenLogicElseBrick'; - } - if (firstCall) { - currentScript.brickList.push(parseBrick(brickList[i])); - } - - positionInScriptBrickList++; - let position = 0; - if (positionInScriptBrickList !== 0) { - position = positionInScriptBrickList - 1; - } - i++; - let list; - if (currentListToFill === null) { - list = currentScript.brickList[position]; - } else { - list = currentListToFill; - } - - let lastIndex = 0; - let listToFill = null; - - while (brickList[i].attributes[0].value !== endBrick) { - if (brickList[i].attributes[0].value === elseBrick && elseBrick !== null) { - i++; - break; - } - if (brickList[i].attributes[0].value === 'IfLogicBeginBrick') { - list.loopOrIfBrickList.push(parseBrick(brickList[i])); - lastIndex = list.loopOrIfBrickList.length - 1; - listToFill = list.loopOrIfBrickList[lastIndex]; - const ifFinished = fillLoopControlBrick( - brickList, - currentScript, - 'IfLogicBeginBrick', - i, - positionInScriptBrickList, - listToFill - ); - i = ifFinished + 1; - } else if (brickList[i].attributes[0].value === 'IfThenLogicBeginBrick') { - list.loopOrIfBrickList.push(parseBrick(brickList[i])); - lastIndex = list.loopOrIfBrickList.length - 1; - listToFill = list.loopOrIfBrickList[lastIndex]; - const ifFinished = fillLoopControlBrick( - brickList, - currentScript, - 'IfThenLogicBeginBrick', - i, - positionInScriptBrickList, - listToFill - ); - i = ifFinished + 1; - } else if (brickList[i].attributes[0].value === 'RepeatBrick') { - list.loopOrIfBrickList.push(parseBrick(brickList[i])); - lastIndex = list.loopOrIfBrickList.length - 1; - listToFill = list.loopOrIfBrickList[lastIndex]; - const loopFinished = fillLoopControlBrick( - brickList, - currentScript, - 'RepeatBrick', - i, - positionInScriptBrickList, - listToFill - ); - i = loopFinished + 1; - } else { - list.loopOrIfBrickList.push(parseBrick(brickList[i])); - i++; - } - } - - if (elseBrick !== null) { - while (brickList[i].attributes[0].value !== endBrick) { - if (brickList[i].attributes[0].value === 'IfLogicBeginBrick') { - list.elseBrickList.push(parseBrick(brickList[i])); - lastIndex = list.elseBrickList.length - 1; - listToFill = list.elseBrickList[lastIndex]; - const ifFinished = fillLoopControlBrick( - brickList, - currentScript, - 'IfLogicBeginBrick', - i, - positionInScriptBrickList, - listToFill - ); - i = ifFinished + 1; - } else if (brickList[i].attributes[0].value === 'IfThenLogicBeginBrick') { - list.elseBrickList.push(parseBrick(brickList[i])); - lastIndex = list.elseBrickList.length - 1; - listToFill = list.elseBrickList[lastIndex]; - const ifFinished = fillLoopControlBrick( - brickList, - currentScript, - 'IfThenLogicBeginBrick', - i, - positionInScriptBrickList, - listToFill - ); - i = ifFinished + 1; - } else if (brickList[i].attributes[0].value === 'RepeatBrick') { - list.elseBrickList.push(parseBrick(brickList[i])); - lastIndex = list.elseBrickList.length - 1; - listToFill = list.elseBrickList[lastIndex]; - const loopFinished = fillLoopControlBrick( - brickList, - currentScript, - 'RepeatBrick', - i, - positionInScriptBrickList, - listToFill - ); - i = loopFinished + 1; - } else { - list.elseBrickList.push(parseBrick(brickList[i])); - i++; - } - } - } - return i; -} - -function parseBrick(brick) { - catLog(brick); - - const name = (brick.getAttribute('type') || 'emptyBlockName').match(/[a-zA-Z0-9]+/)[0]; - - let brickId = null; - const idTag = brick.getElementsByTagName('brickId'); - if (idTag && idTag.length >= 1) { - brickId = idTag[0].innerHTML; - if (brickId) { - brickId = brickId.trim(); - } - } - - const currentBrick = new Brick(name, brickId); - - for (let i = 0; i < brick.childNodes.length; i++) { - checkUsage(brick.childNodes[i], currentBrick); - } - - if (currentBrick.userBrickId) { - currentBrick.name = currentBrick.userBrickId; - } - - return currentBrick; -} - -/** - * Return crowdin value or default - * @param {*} key - * @param {*} def - */ -const getMsgValueOrDefault = (key, def = '') => { - if (key === undefined) { - return def; - } - const msgValue = CatBlocksMsgs.getTranslationForKey(key); - return msgValue ? msgValue : def; -}; - -/** - * Return node value or default - * @param {*} node - * @param {*} def - */ -const getNodeValueOrDefault = (node, def = '') => { - if (node === undefined || node.nodeValue === undefined) { - return def; - } - return node.nodeValue; -}; - -function checkUsage(list, location) { - switch (list.nodeName) { - case 'broadcastMessage': - case 'spriteToBounceOffName': - case 'receivedMessage': - case 'sceneToStart': - case 'sceneForTransition': { - location.formValues.set('DROPDOWN', getNodeValueOrDefault(list.childNodes[0])); - break; - } - case 'pointedObject': { - const brickName = list.getAttribute('name'); - location.formValues.set('DROPDOWN', brickName); - break; - } - case 'eye': { - const eye = getNodeValueOrDefault(list.childNodes[0]); - location.formValues.set('eye', getMsgValueOrDefault(`PHIRO_EYE_${eye}`)); - break; - } - case 'tone': { - const tone = getNodeValueOrDefault(list.childNodes[0], 'DO'); - location.formValues.set('tone', getMsgValueOrDefault(`PHIRO_TONE_${tone}`)); - break; - } - - case 'objectToClone': { - if (list.children[0] != null && list.children[0].children[0]) { - location.formValues.set('SPINNER', list.children[0].children[0].attributes.name.value); - } else { - location.formValues.set('SPINNER', getNodeValueOrDefault(list.childNodes[0])); - } - break; - } - - case 'spinnerSelectionFRONT': { - const key = getNodeValueOrDefault(list.childNodes[0]); - if (key == 'true') { - location.formValues.set('SPINNER', getMsgValueOrDefault(`CAMCHOOSESPINNER_1`)); - } else { - location.formValues.set('SPINNER', getMsgValueOrDefault(`CAMCHOOSESPINNER_0`)); - } - break; - } - - case 'spinnerSelectionON': { - const key = getNodeValueOrDefault(list.childNodes[0]); - if (key == 'true') { - location.formValues.set('SPINNER', getMsgValueOrDefault(`CAMSPINNER_1`)); - } else { - location.formValues.set('SPINNER', getMsgValueOrDefault(`CAMSPINNER_0`)); - } - break; - } - - case 'ledStatus': - case 'soundName': - case 'motor': { - location.formValues.set('DROPDOWN', getMsgValueOrDefault(list.childNodes[0].nodeValue)); - break; - } - - case 'eventValue': { - const value = getNodeValueOrDefault(list.childNodes[0]); - if (list.parentElement.getAttribute('type') === 'RaspiInterruptScript') { - if (value === 'pressed') { - location.formValues.set('eventValue', getMsgValueOrDefault('RASPI_PRESSED')); - } else if (value === 'released') { - location.formValues.set('eventValue', getMsgValueOrDefault('RASPI_RELEASED')); - } - } else { - location.formValues.set('eventValue', getNodeValueOrDefault(value)); - } - break; - } - case 'pin': { - location.formValues.set('pin', getNodeValueOrDefault(list.childNodes[0])); - break; - } - - case 'spinnerSelectionID': { - const brickName = list.parentElement.getAttribute('type'); - const key = getNodeValueOrDefault(list.childNodes[0]); - if (brickName === 'CameraBrick') { - location.formValues.set('SPINNER', getMsgValueOrDefault(`CAMSPINNER_${key}`, key)); - } else if (brickName === 'ReadVariableFromFileBrick') { - location.formValues.set('SPINNER', getMsgValueOrDefault(`READ_VARIABLE_${key}`, key)); - } else { - location.formValues.set('SPINNER', getMsgValueOrDefault(`FLASHSPINNER_${key}`, key)); - } - break; - } - - case 'sensorSpinnerPosition': { - location.formValues.set('DROPDOWN', getMsgValueOrDefault(`SPINNER_PHIRO_${list.childNodes[0].textContent}`)); - break; - } - - case 'nfcTagNdefType': { - location.formValues.set('DROPDOWN', getMsgValueOrDefault('TNF_' + list.childNodes[0].textContent)); - break; - } - - case 'fadeSpinnerSelectionId': { - const brickName = list.parentElement.getAttribute('type'); - const key = getNodeValueOrDefault(list.childNodes[0]); - - if (brickName === 'FadeParticleEffectBrick') { - location.formValues.set('brick_fade_particle_effect_spinner', getMsgValueOrDefault(`FADESPINNER_${key}`, key)); - } else if (brickName === 'ParticleEffectAdditivityBrick') { - location.formValues.set( - 'brick_additive_particle_effect_spinner', - getMsgValueOrDefault(`PARTICLESPINNER_${key}`, key) - ); - } - break; - } - - case 'type': { - const key = getNodeValueOrDefault(list.childNodes[0]); - location.formValues.set('DROPDOWN', getMsgValueOrDefault(`GRAVITY_${key}`, key)); - break; - } - - case 'spinnerSelection': { - const key = getNodeValueOrDefault(list.childNodes[0]); - const brickName = list.parentElement.getAttribute('type'); - if (brickName === 'GoToBrick') { - if (key === '80') { - location.formValues.set('SPINNER', getMsgValueOrDefault('GO_TO_TOUCH_POSITION', key)); - } else if (key === '81') { - location.formValues.set('SPINNER', getMsgValueOrDefault('GO_TO_RANDOM_POSITION', key)); - } else if (key === '82') { - const children = list.parentElement.childNodes; - for (let j = 0; j < children.length; j++) { - if (children[j].nodeName === 'destinationSprite') { - const name = children[j].getAttribute('name'); - location.formValues.set('SPINNER', name); - break; - } - } - } - } else { - location.formValues.set('SPINNER', getMsgValueOrDefault(`SPINNER_${key}`, key)); - } - break; - } - - case 'alignmentSelection': { - const key = getNodeValueOrDefault(list.childNodes[0]); - location.formValues.set('ALIGNMENT', getMsgValueOrDefault(`ALIGNMENTS_${key}`, key)); - break; - } - - case 'ledAnimationName': { - const key = getNodeValueOrDefault(list.childNodes[0]); - location.formValues.set('ADRONEANIMATION', getMsgValueOrDefault(key, key)); - break; - } - - case 'animationName': { - const key = getNodeValueOrDefault(list.childNodes[0]); - location.formValues.set('ANIMATION', getMsgValueOrDefault(`ANIMATION_${key}`, key)); - break; - } - - case 'selection': { - const key = getNodeValueOrDefault(list.childNodes[0]); - location.formValues.set('SPINNER', getMsgValueOrDefault(`POINTTO_${key}`, key)); - break; - } - - case 'formulaMap': - case 'formulaList': { - const formulaList = list.children; - for (let j = 0; j < formulaList.length; j++) { - const formula = new Formula(); - workFormula(formula, formulaList[j]); - let attribute; - if (formulaList[j].hasAttribute('input')) { - attribute = formulaList[j].getAttribute('input'); - } else { - attribute = formulaList[j].getAttribute('category'); - } - location.formValues.set(attribute, Formula.stringify(formula)); - } - break; - } - - case 'ifBranchBricks': - case 'loopBricks': { - const loopOrIfBrickList = list.children; - for (let j = 0; j < loopOrIfBrickList.length; j++) { - location.loopOrIfBrickList.push(parseBrick(loopOrIfBrickList[j])); - } - break; - } - - case 'elseBranchBricks': { - const elseBrickList = list.children; - for (let j = 0; j < elseBrickList.length; j++) { - location.elseBrickList.push(parseBrick(elseBrickList[j])); - } - break; - } - - case 'sound': - case 'look': { - const node = flatReference(list); - const name = node.getAttribute('name'); - location.formValues.set(list.nodeName, name); - break; - } - - case 'userVariable': - case 'userList': { - const node = flatReference(list); - const nodeName = node.querySelector(`${list.nodeName} name`) - ? node.querySelector(`${list.nodeName} name`).textContent - : 'node'; - location.formValues.set('DROPDOWN', nodeName); - break; - } - - case 'userLists': { - if (list.parentElement.getAttribute('type') === 'ParameterizedBrick') { - const lengthOfList = list.children.length; - let message = - lengthOfList === 1 - ? getMsgValueOrDefault(`ASSERTION_PARAMETERIZED_LIST_ONE`, 'list') - : getMsgValueOrDefault(`ASSERTION_PARAMETERIZED_LIST_OTHER`, 'lists'); - message = message.replaceAll('%d', lengthOfList); - location.formValues.set('CATBLOCKS_ASSERT_LISTS_SELECTED', message); - } - break; - } - - case 'userDataList': { - const userDataList = list.children; - for (let j = 0; j < userDataList.length; j++) { - const userDataElement = flatReference(userDataList[j]); - const userDataCategory = userDataList[j].getAttribute('category'); - let userDataName = null; - if (userDataElement.getElementsByTagName('name').length != 0) { - userDataName = userDataElement.getElementsByTagName('name')[0].innerHTML; - } - location.formValues.set(userDataCategory, userDataName); - } - break; - } - - case 'userDefinedBrickID': { - location.userBrickId = list.innerHTML; - break; - } - - case 'commentedOut': { - location.commentedOut = list.innerHTML == 'true'; - break; - } - - case 'instrumentSelection': - case 'drumSelection': { - const key = getNodeValueOrDefault(list.childNodes[0]); - const value = key.toLowerCase().replaceAll('_', ' '); - location.formValues.set('DROPDOWN', value); - break; - } - - case 'endBrick': { - if (list.parentElement.getAttribute('type') === 'ParameterizedBrick') { - const children = list.children; - for (let j = 0; j < children.length; j++) { - if (children[j].nodeName === 'formulaList') { - const formulaList = children[j].children; - for (let j = 0; j < formulaList.length; j++) { - const formula = new Formula(); - workFormula(formula, formulaList[j]); - const attribute = formulaList[j].getAttribute('category'); - location.formValues.set(attribute, Formula.stringify(formula)); - } - } else if (children[j].nodeName === 'userList') { - const node = flatReference(children[j]); - const name = node.children[2].innerHTML; - location.formValues.set('LIST_SELECTED', name); - } - } - } - break; - } - - case 'screenRefresh': { - const key = getNodeValueOrDefault(list.childNodes[0]).toUpperCase(); - location.formValues.set('UDB_SCREEN_REFRESH', getMsgValueOrDefault(`UDB_SCREEN_REFRESH_${key}`, key)); - break; - } - - default: - } -} - -function workFormula(formula, input) { - for (let i = 0; i < input.childNodes.length; i++) { - if (input.childNodes[i].nodeName === 'additionalChildren') { - if (input.childNodes[i].hasChildNodes()) { - if (input.childNodes[i].childNodes[1].nodeName === 'org.catrobat.catroid.formulaeditor.FormulaElement') { - const newFormula = new Formula(); - formula.setMid(newFormula); - workFormula(newFormula, input.childNodes[i].childNodes[1]); - } - } - } - if (input.childNodes[i].nodeName === 'leftChild') { - const newFormula = new Formula(); - formula.setLeft(newFormula); - workFormula(newFormula, input.childNodes[i]); - } - if (input.childNodes[i].nodeName === 'rightChild') { - const newFormula = new Formula(); - formula.setRight(newFormula); - workFormula(newFormula, input.childNodes[i]); - } - - if (input.childNodes[i].nodeName === 'type') { - const typeValue = input.childNodes[i].innerHTML; - if ( - typeValue === 'BRACKET' || - typeValue === 'USER_LIST' || - typeValue === 'STRING' || - typeValue === 'NUMBER' || - typeValue === 'USER_VARIABLE' || - typeValue === 'USER_DEFINED_BRICK_INPUT' - ) { - formula.operator = typeValue; - } - } - if (input.childNodes[i].nodeName === 'value') { - const operatorKey = getNodeValueOrDefault(input.childNodes[i].childNodes[0]); - if ( - formula.operator !== 'USER_LIST' && - formula.operator !== 'STRING' && - formula.operator !== 'NUMBER' && - formula.operator !== 'USER_VARIABLE' && - formula.optor !== 'USER_DEFINED_BRICK_INPUT' - ) { - formula.operator = operatorKey; - } - formula.value = getMsgValueOrDefault(operatorKey, operatorKey); - } - } -} - -/** - * Export Parser class - * Only those methods are visible outside this module - */ -export class Parser { - /** - * For performance reasons only the requested object is parsed. - * The xml is filtered the the selected object is parsed. - * - * @static - * @param {string} xmlString code.xml as string - * @param {*} sceneName name of the scene containing the object to render - * @param {*} objectName name of the object to render - * @memberof Parser - */ - static convertObjectToJSON(xmlString, sceneName, objectName) { - if (typeof xmlString === 'string') { - try { - const xml = new window.DOMParser().parseFromString(xmlString.trim(), 'text/xml'); - if (!isSupported(xml)) { - return undefined; - } - - const xpath = `/program/scenes/scene[name='${sceneName}']/objectList`; - const xpathResult = xml.evaluate(xpath, xml, null, XPathResult.ANY_TYPE, null); - if (!xpathResult) { - return undefined; - } - - const objectListTag = xpathResult.iterateNext(); - if (!objectListTag) { - return undefined; - } - - let objectTag = null; - - const objectTagResult = xml.evaluate('object', objectListTag); - let currentObjectNode = objectTagResult.iterateNext(); - while (currentObjectNode) { - const flatObjectNode = flatReference(currentObjectNode, xml); - if (flatObjectNode.hasAttribute('name')) { - if (flatObjectNode.getAttribute('name') == objectName) { - objectTag = flatObjectNode; - break; - } - } - currentObjectNode = objectTagResult.iterateNext(); - } - - if (!objectTag) { - return undefined; - } - - initParser(xml); - return parseObjects(objectTag); - } catch (e) { - catLog(e); - console.error( - `Failed to convert catroid program given as string into a XMLDocument, please verify that the string is a valid program` - ); - return undefined; - } - } - } - - /** - * Convert given XML to JSON object - * @static - * @param {Element} xmlString code.xml file - * @returns {Object} - */ - static convertProgramToJSON(xmlString) { - if (typeof xmlString === 'string') { - try { - const xml = new window.DOMParser().parseFromString(xmlString, 'text/xml'); - if (!isSupported(xml)) { - return undefined; - } - - initParser(xml); - return getCatroidProgramObject(xml); - } catch (e) { - catLog(e); - console.error( - `Failed to convert catroid program given as string into a XMLDocument, please verify that the string is a valid program` - ); - return undefined; - } - } - return getCatroidProgramObject(xmlString); - } - - /** - * Convert given XML to JSON object and return every error - * @static - * @param {XMLDocument | string} xmlString code.xml file - * @returns {Object} - */ - static convertProgramToJSONDebug(xmlString) { - const obj = Parser.convertProgramToJSON(xmlString); - - if (obj === undefined) { - const xml = new window.DOMParser().parseFromString(xmlString, 'text/xml'); - - const appVersion = xml.getElementsByTagName('catrobatLanguageVersion'); - if (appVersion === undefined || appVersion.length < 1) { - throw new Error(`Found program version "${appVersion}", minimum supported is ${supportedAppVersion}`); - } else if (appVersion[0].innerHTML < supportedAppVersion) { - throw new Error( - `Found program version ${appVersion[0].innerHTML}, minimum supported is ${supportedAppVersion}` - ); - } - - initParser(xml); - return getCatroidProgramObject(xml); - } - - return obj; - } -} diff --git a/src/common/ts/parser/CatblocksBrick.ts b/src/common/ts/parser/CatblocksBrick.ts new file mode 100644 index 00000000..b3aff394 --- /dev/null +++ b/src/common/ts/parser/CatblocksBrick.ts @@ -0,0 +1,15 @@ +export class CatblocksBrick { + name: string; + id: string | null = null; + loopOrIfBrickList: CatblocksBrick[] = new Array(); + elseBrickList: CatblocksBrick[] = new Array(); + colorVariation = 0; + commentedOut = false; + userDefinedBrickID: string | null = null; + formValues: Map = new Map(); + + constructor(name: string, id: string | null) { + this.name = name; + this.id = id; + } +} diff --git a/src/common/ts/parser/CatblocksFile.ts b/src/common/ts/parser/CatblocksFile.ts new file mode 100644 index 00000000..ca6be86c --- /dev/null +++ b/src/common/ts/parser/CatblocksFile.ts @@ -0,0 +1,9 @@ +export class CatblocksFile { + name: string; + path: string; + + constructor(name: string, path: string) { + this.name = name; + this.path = path; + } +} diff --git a/src/common/ts/parser/CatblocksObject.ts b/src/common/ts/parser/CatblocksObject.ts new file mode 100644 index 00000000..fc1e3f08 --- /dev/null +++ b/src/common/ts/parser/CatblocksObject.ts @@ -0,0 +1,15 @@ +import { CatblocksFile } from './CatblocksFile'; +import { CatblocksScript } from './CatblocksScript'; +import { UserDefinedBrickDefinition } from './UserDefinedBrick'; + +export class CatblocksObject { + name: string; + scriptList: CatblocksScript[] = new Array(); + lookList: CatblocksFile[] = new Array(); + soundList: CatblocksFile[] = new Array(); + userDefinedBricks: UserDefinedBrickDefinition[] = new Array(); + + constructor(name: string) { + this.name = name; + } +} diff --git a/src/common/ts/parser/CatblocksProject.ts b/src/common/ts/parser/CatblocksProject.ts new file mode 100644 index 00000000..dcd20d49 --- /dev/null +++ b/src/common/ts/parser/CatblocksProject.ts @@ -0,0 +1,10 @@ +import { CatblocksScene } from './CatblocksScene'; + +export class CatblocksProject { + scenes: CatblocksScene[] = new Array(); + programName = ''; + + constructor(programName: string) { + this.programName = programName; + } +} diff --git a/src/common/ts/parser/CatblocksScene.ts b/src/common/ts/parser/CatblocksScene.ts new file mode 100644 index 00000000..84328cba --- /dev/null +++ b/src/common/ts/parser/CatblocksScene.ts @@ -0,0 +1,10 @@ +import { CatblocksObject } from './CatblocksObject'; + +export class CatblocksScene { + name: string; + objectList: CatblocksObject[] = new Array(); + + constructor(name: string) { + this.name = name; + } +} diff --git a/src/common/ts/parser/CatblocksScript.ts b/src/common/ts/parser/CatblocksScript.ts new file mode 100644 index 00000000..fb3b1e4b --- /dev/null +++ b/src/common/ts/parser/CatblocksScript.ts @@ -0,0 +1,11 @@ +import { CatblocksBrick } from './CatblocksBrick'; + +export class CatblocksScript extends CatblocksBrick { + posX: number | null = null; + posY: number | null = null; + brickList: CatblocksBrick[] = new Array(); + + constructor(brickType: string, scriptId: string | null) { + super(brickType, scriptId); + } +} diff --git a/src/common/ts/parser/CatblocksSpinnerProperties.ts b/src/common/ts/parser/CatblocksSpinnerProperties.ts new file mode 100644 index 00000000..faf48453 --- /dev/null +++ b/src/common/ts/parser/CatblocksSpinnerProperties.ts @@ -0,0 +1,11 @@ +export class CatblocksSpinnerProperties { + name: string; + valueXPath: string[]; + messageFormat: string; + + constructor(name: string, valueXPath: string[], messageFormat: string) { + this.name = name; + this.valueXPath = valueXPath; + this.messageFormat = messageFormat; + } +} diff --git a/src/common/ts/parser/Formula.ts b/src/common/ts/parser/Formula.ts new file mode 100644 index 00000000..036cbacb --- /dev/null +++ b/src/common/ts/parser/Formula.ts @@ -0,0 +1,205 @@ +import { ParserUtils } from './ParserUtils'; + +export abstract class Formula { + protected static simpleFormulaTypes: readonly string[] = [ + 'NUMBER', + 'STRING', + 'SENSOR', + 'USER_LIST', + 'USER_VARIABLE', + 'USER_DEFINED_BRICK_INPUT' + ]; + + protected type: string; + protected value: string; + + constructor(type: string, value: string) { + this.type = type; + this.value = value; + } + + public static parse(formulaNode: Element, codeXml: Document): Formula { + let value = ''; + const valueNode = ParserUtils.getElementByXPath(codeXml, 'value', formulaNode); + if (valueNode) { + value = valueNode.textContent ?? ''; + } + + const typeNode = ParserUtils.getElementByXPath(codeXml, 'type', formulaNode); + if (typeNode) { + const strType = ParserUtils.trimTextContent(typeNode.textContent); + if (Formula.simpleFormulaTypes.includes(strType)) { + return SimpleFormula.parseSimpleFormula(strType, value); + } else { + return ComplexFormula.parseComplexFormula(value, strType, formulaNode, codeXml); + } + } + throw new Error('No valid forumla type found.'); + } + + public abstract toFormulaString(): string; +} + +class SimpleFormula extends Formula { + private static readonly defaultSimpleFormulaFormat: string = '%v'; + private static readonly simpleFormulaFormats: Map = new Map([ + ['USER_LIST', '*%v*'], + ['STRING', "'%v'"], + ['USER_VARIABLE', '"%v"'], + ['USER_DEFINED_BRICK_INPUT', '[%v]'] + ]); + + constructor(type: string, value: string) { + super(type, value); + } + + public static parseSimpleFormula(type: string, value: string): SimpleFormula { + return new SimpleFormula(type, value); + } + + public override toFormulaString(): string { + const formulaFormat: string = + SimpleFormula.simpleFormulaFormats.get(this.type) ?? SimpleFormula.defaultSimpleFormulaFormat; + const messageValue = ParserUtils.getMessage(this.value, this.value); + return formulaFormat.replace('%v', messageValue); + } +} + +class ComplexFormula extends Formula { + private static readonly defaultComplexFormulaFormat: string = '%l %v %r'; + private static readonly complexFormulaFormats: Map = new Map([ + ['BRACKET', '(%l%r)'], + ['SIN', '%v(%l)'], + ['COS', '%v(%l)'], + ['TAN', '%v(%l)'], + ['LN', '%v(%l)'], + ['LOG', '%v(%l)'], + ['ABS', '%v(%l)'], + ['ROUND', '%v(%l)'], + ['ARCSIN', '%v(%l)'], + ['ARCCOS', '%v(%l)'], + ['ARCTAN', '%v(%l)'], + ['FLOOR', '%v(%l)'], + ['CEIL', '%v(%l)'], + ['EXP', '%v(%l)'], + ['SQRT', '%v(%l)'], + ['MULTI_FINGER_X', '%v(%l)'], + ['MULTI_FINGER_Y', '%v(%l)'], + ['MULTI_FINGER_TOUCHED', '%v(%l)'], + ['TEXT_BLOCK_X', '%v(%l)'], + ['TEXT_BLOCK_Y', '%v(%l)'], + ['TEXT_BLOCK_SIZE', '%v(%l)'], + ['TEXT_BLOCK_LANGUAGE_FROM_CAMERA', '%v(%l)'], + ['ARDUINOANALOG', '%v(%l)'], + ['ARDUINODIGITAL', '%v(%l)'], + ['ARCTAN2', '%v(%l, %r)'], + ['POWER', '%v(%l, %r)'], + ['RASPIDIGITAL', '%v(%l)'], + ['MOD', '%v(%l, %r)'], + ['RAND', '%v(%l, %r)'], + ['MAX', '%v(%l, %r)'], + ['MIN', '%v(%l, %r)'], + ['IF_THEN_ELSE', '%v(%l, %r, %m)'], + ['LENGTH', '%v(%l)'], + ['LETTER', '%v(%l, %r)'], + ['JOIN', '%v(%l, %r)'], + ['JOIN3', '%v(%l, %r, %m)'], + ['FLATTEN', '%v(%l)'], + ['INDEX_OF_ITEM', '%v(%l, %r)'], + ['INDEX_CURRENT_TOUCH', '%v(%l)'], + ['COLOR_AT_XY', '%v(%l, %r)'], + ['COLOR_TOUCHES_COLOR', '%v(%l, %r)'], + ['COLOR_EQUALS_COLOR', '%v(%l, %r)'], + ['COLLIDES_WITH_COLOR', '%v(%l)'], + ['REGEX', '%v(%l, %r)'], + ['CONTAINS', '%v(%l, %r)'], + ['NUMBER_OF_ITEMS', '%v(%l)'], + ['LIST_ITEM', '%v(%l, %r)'] + ]); + + leftChild: Formula | null = null; + rightChild: Formula | null = null; + middleChild: Formula | null = null; + + constructor( + type: string, + value: string, + leftChild: Formula | null, + rightChild: Formula | null = null, + middleChild: Formula | null = null + ) { + super(type, value); + this.leftChild = leftChild; + this.rightChild = rightChild; + this.middleChild = middleChild; + } + + public static parseComplexFormula( + type: string, + value: string, + formulaNode: Element, + codeXml: Document + ): ComplexFormula { + const leftChildNode = ParserUtils.getElementByXPath(codeXml, 'leftChild', formulaNode); + const rightChildNode = ParserUtils.getElementByXPath(codeXml, 'rightChild', formulaNode); + const additionalChildrenNode = ParserUtils.getElementByXPath(codeXml, 'additionalChildren', formulaNode); + + let leftFormula: Formula | null = null; + let rightFormula: Formula | null = null; + let middleFormula: Formula | null = null; + + if (leftChildNode) { + leftFormula = Formula.parse(leftChildNode, codeXml); + } + if (rightChildNode) { + rightFormula = Formula.parse(rightChildNode, codeXml); + } + if (additionalChildrenNode) { + const additionalChild = ParserUtils.getElementByXPath( + codeXml, + 'org.catrobat.catroid.formulaeditor.FormulaElement', + additionalChildrenNode + ); + if (additionalChild) { + middleFormula = Formula.parse(additionalChild, codeXml); + } + } + return new ComplexFormula(value, type, leftFormula, rightFormula, middleFormula); + } + + public override toFormulaString(): string { + const valueString: string = ParserUtils.getMessage(this.value, this.value); + + let leftString = ''; + let middleString = ''; + let rightString = ''; + if (this.leftChild) { + leftString = this.leftChild.toFormulaString(); + } + if (this.rightChild) { + rightString = this.rightChild.toFormulaString(); + } + if (this.middleChild) { + middleString = this.middleChild.toFormulaString(); + } + + let formulaFormat = ComplexFormula.complexFormulaFormats.get(this.type); + if (!formulaFormat) { + formulaFormat = + ComplexFormula.complexFormulaFormats.get(this.value) ?? ComplexFormula.defaultComplexFormulaFormat; + } + if (formulaFormat === ComplexFormula.defaultComplexFormulaFormat) { + if (!leftString) { + formulaFormat = formulaFormat.replace('%l ', ''); + } + if (!rightString) { + formulaFormat = formulaFormat.replace(' %r', ''); + } + } + return formulaFormat + .replace('%v', valueString) + .replace('%l', leftString) + .replace('%r', rightString) + .replace('%m', middleString); + } +} diff --git a/src/common/ts/parser/Parser.ts b/src/common/ts/parser/Parser.ts new file mode 100644 index 00000000..e4d682c4 --- /dev/null +++ b/src/common/ts/parser/Parser.ts @@ -0,0 +1,536 @@ +import { ParserUtils } from './ParserUtils'; +import { Formula } from './Formula'; +import { getBrickSpinnerProperties } from '../../../library/js/blocks/bricks'; +import { CatblocksBrick } from './CatblocksBrick'; +import { CatblocksFile } from './CatblocksFile'; +import { CatblocksObject } from './CatblocksObject'; +import { CatblocksProject } from './CatblocksProject'; +import { CatblocksScene } from './CatblocksScene'; +import { CatblocksScript } from './CatblocksScript'; +import { UserDefinedBrickDefinition } from './UserDefinedBrick'; +import { UserDefinedBrickInputType } from './UserDefinedBrickInputType'; +import { CatblocksSpinnerProperties } from './CatblocksSpinnerProperties'; + +export class CatblocksParser { + private strCodeXml: string; + private codeXml: Document; + + private isOldMultiPartBrickFormat = false; + + constructor(strCodeXml = '') { + this.strCodeXml = strCodeXml; + this.codeXml = new Document(); + if (this.strCodeXml) { + this.parseXmlString(strCodeXml); + } + } + + public parseXmlString(strCodeXml: string): void { + this.strCodeXml = strCodeXml; + if (this.strCodeXml) { + const parser = new DOMParser(); + this.codeXml = parser.parseFromString(strCodeXml, 'application/xml'); + } + } + + public xmlToCatblocksProject(): CatblocksProject { + if (!this.strCodeXml) { + throw new Error('Please initialize the program xml first!'); + } + + this.assureProgramVersionSupported(); + + const programNameNode = ParserUtils.getElementByXPath(this.codeXml, '/program/header/programName', this.codeXml); + if (!programNameNode) { + throw new Error('Program name not found'); + } + + const programName = programNameNode.textContent ?? ''; + const catblocksProject = new CatblocksProject(programName); + + ParserUtils.foreachElementByXPath(this.codeXml, '/program/scenes/scene', this.codeXml, (sceneNode: Element) => { + const scene = this.parseScene(sceneNode); + if (scene) { + catblocksProject.scenes.push(scene); + } + }); + + return catblocksProject; + } + + public xmlToCatblocksObject(sceneNameToParse: string, objectNameToParse: string): CatblocksObject | null { + if (!this.strCodeXml) { + throw new Error('Please initialize the program xml first!'); + } + this.assureProgramVersionSupported(); + + let parsedObject: CatblocksObject | null = null; + ParserUtils.foreachElementByXPath(this.codeXml, '/program/scenes/scene', this.codeXml, (sceneNode: Element) => { + const sceneName = ParserUtils.getElementByXPath(this.codeXml, 'name', sceneNode)?.textContent ?? ''; + if (sceneName === sceneNameToParse) { + ParserUtils.foreachElementByXPath(this.codeXml, 'objectList/object', sceneNode, (objectNode: Element) => { + const objectName = ParserUtils.getAttrByXPath(this.codeXml, '@name', objectNode)?.textContent; + if (objectName === objectNameToParse) { + parsedObject = this.parseObject(objectNode); + return; + } + }); + if (parsedObject) { + return; + } + } + }); + if (!parsedObject) { + throw new Error(`The object ${objectNameToParse} was not found in scene ${sceneNameToParse}`); + } + return parsedObject; + } + + private assureProgramVersionSupported() { + const programVersionNode = ParserUtils.getElementByXPath( + this.codeXml, + '/program/header/catrobatLanguageVersion', + this.codeXml + ); + if (!programVersionNode) { + throw new Error('Program version not found'); + } + const strProgramVersion = ParserUtils.trimTextContent(programVersionNode.textContent); + if (!strProgramVersion) { + throw new Error('Program version is empty'); + } + const programVersion = parseFloat(strProgramVersion); + if (programVersion < 0.9994) { + throw new Error(`Found program version ${programVersion}, minimum supported is 0.9994`); + } + } + + private parseScene(sceneNode: Element): CatblocksScene | null { + const sceneName = ParserUtils.getElementByXPath(this.codeXml, 'name', sceneNode)?.textContent ?? ''; + if (!sceneName) { + return null; + } + const scene = new CatblocksScene(sceneName); + + ParserUtils.foreachElementByXPath(this.codeXml, 'objectList/object', sceneNode, (objectNode: Element) => { + const object = this.parseObject(objectNode); + if (object) { + scene.objectList.push(object); + } + }); + + return scene; + } + + private parseObject(objectNode: Element): CatblocksObject | null { + const objectName = objectNode.getAttribute('name'); + if (!objectName) { + return null; + } + const object = new CatblocksObject(objectName); + + ParserUtils.foreachElementByXPath(this.codeXml, 'lookList/look', objectNode, (lookNode: Element) => { + const look = this.parseCatblocksFile(lookNode); + if (look) { + object.lookList.push(look); + } + }); + + ParserUtils.foreachElementByXPath(this.codeXml, 'soundList/sound', objectNode, (soundNode: Element) => { + const sound = this.parseCatblocksFile(soundNode); + if (sound) { + object.soundList.push(sound); + } + }); + + ParserUtils.foreachElementByXPath( + this.codeXml, + 'userDefinedBrickList/brick', + objectNode, + (userBrickNode: Element) => { + const userDefinedBrick = this.parseUserDefinedBrickDefinitions(userBrickNode); + if (userDefinedBrick) { + object.userDefinedBricks.push(userDefinedBrick); + } + } + ); + + ParserUtils.foreachElementByXPath(this.codeXml, 'scriptList/script', objectNode, (scriptNode: Element) => { + const script = this.parseScript(scriptNode); + if (!script) { + return; + } + object.scriptList.push(script); + }); + + return object; + } + + private parseCatblocksFile(fileNode: Element): CatblocksFile | null { + const filename = fileNode.getAttribute('fileName'); + const name = fileNode.getAttribute('name'); + if (name && filename) { + return new CatblocksFile(name, filename); + } + return null; + } + + private parseUserDefinedBrickDefinitions(brickElement: Element): UserDefinedBrickDefinition | null { + const userDefinedBrickID = ParserUtils.trimTextContent( + ParserUtils.getElementByXPath(this.codeXml, 'userDefinedBrickID', brickElement)?.textContent + ); + if (!userDefinedBrickID) { + return null; + } + const udbDefinition = new UserDefinedBrickDefinition(userDefinedBrickID); + + const userBrickDataListNode = ParserUtils.getElementByXPath(this.codeXml, 'userDefinedBrickDataList', brickElement); + if (!userBrickDataListNode) { + return null; + } + + let inputCounter = 1; + if (!userBrickDataListNode?.children) { + return null; + } + for (const childNode of userBrickDataListNode.children) { + const dataNode = ParserUtils.flattenElementReference(this.codeXml, childNode); + const dataType = ParserUtils.trimTextContent( + ParserUtils.getElementByXPath(this.codeXml, 'type', dataNode)?.textContent + ); + if (dataType?.toUpperCase() === 'LABEL') { + const labelText = ParserUtils.trimTextContent( + ParserUtils.getElementByXPath(this.codeXml, 'label', dataNode)?.textContent + ); + udbDefinition.message += `${labelText} `; + } else if (dataType?.toUpperCase() === 'INPUT') { + const inputName = ParserUtils.trimTextContent( + ParserUtils.getElementByXPath(this.codeXml, 'input/input', dataNode)?.textContent + ); + if (inputName) { + udbDefinition.inputTypes.push(new UserDefinedBrickInputType(dataType, inputName)); + udbDefinition.message += `%${inputCounter++}%${inputCounter++} `; + } + } + } + udbDefinition.message = udbDefinition.message.trim(); + + return udbDefinition; + } + + private parseScript(scriptNode: Element): CatblocksScript | null { + const scriptType = scriptNode.getAttribute('type'); + const strScriptPosX = scriptNode.getAttribute('posX'); + const strScriptPosY = scriptNode.getAttribute('posY'); + + if (!scriptType) { + return null; + } + + const scriptIdNode = ParserUtils.getElementByXPath(this.codeXml, 'scriptId', scriptNode); + let scriptId: string | null = null; + if (scriptIdNode) { + scriptId = ParserUtils.trimTextContent(scriptIdNode.textContent); + } + + const script = new CatblocksScript(scriptType, scriptId); + if (strScriptPosX && strScriptPosY) { + script.posX = parseFloat(strScriptPosX); + script.posY = parseFloat(strScriptPosY); + } + script.commentedOut = false; + const commentedOutNode = ParserUtils.getElementByXPath(this.codeXml, 'commentedOut', scriptNode); + if (commentedOutNode) { + if (ParserUtils.trimTextContent(commentedOutNode.textContent).toLowerCase() === 'true') { + script.commentedOut = true; + } + } + + const userDefinedBrickIDNode = ParserUtils.getElementByXPath(this.codeXml, 'userDefinedBrickID', scriptNode); + if (userDefinedBrickIDNode) { + script.userDefinedBrickID = ParserUtils.trimTextContent(userDefinedBrickIDNode.textContent); + } + + this.parseFormulas(scriptNode, script); + this.parseSpinners(scriptNode, script); + + ParserUtils.foreachElementByXPath(this.codeXml, 'brickList/brick', scriptNode, (brickNode: Element) => { + const brick = this.parseBrick(brickNode); + if (brick) { + script.brickList.push(brick); + } + }); + + this.fixBrickHierarchy(script); + + return script; + } + + private fixBrickHierarchy(script: CatblocksScript) { + if (!this.isOldMultiPartBrickFormat) { + return; + } + + const mainBricks: Array = []; + const brickStack: Array> = []; + + for (let i = 0; i < script.brickList.length; i++) { + const brick = script.brickList[i]; + if ( + brick.name === 'RepeatBrick' || + brick.name === 'IfLogicBeginBrick' || + brick.name === 'IfThenLogicBeginBrick' + ) { + if (mainBricks.length > 0) { + script.brickList.splice(i, 1); + i--; + } + mainBricks.push(brick); + brickStack.push([]); + continue; + } + + if (brick.name === 'LoopEndBrick' || brick.name === 'IfLogicElseBrick' || brick.name === 'IfThenLogicElseBrick') { + const topBrick = mainBricks.pop(); + if (topBrick) { + topBrick.loopOrIfBrickList = brickStack.pop() || []; + if (brick.name === 'IfLogicElseBrick' || brick.name === 'IfThenLogicElseBrick') { + mainBricks.push(topBrick); + brickStack.push([]); + } else { + const newParent = brickStack.pop(); + if (newParent) { + newParent.push(topBrick); + brickStack.push(newParent); + } + } + } + script.brickList.splice(i, 1); + i--; + } else if (brick.name === 'IfLogicEndBrick' || brick.name === 'IfThenLogicEndBrick') { + const topBrick = mainBricks.pop(); + if (topBrick) { + topBrick.elseBrickList = brickStack.pop() || []; + const newParent = brickStack.pop(); + if (newParent) { + newParent.push(topBrick); + brickStack.push(newParent); + } + } + script.brickList.splice(i, 1); + i--; + } else { + const currentStack = brickStack.pop(); + if (currentStack) { + brickStack.push(currentStack); + currentStack.push(script.brickList[i]); + script.brickList.splice(i, 1); + i--; + } + } + } + } + + private parseBrick(brickNode: Element): CatblocksBrick | null { + let brickType = brickNode.getAttribute('type'); + if (!brickType) { + return null; + } + + if (brickType === 'LoopEndBrick' || brickType === 'IfLogicEndBrick' || brickType === 'IfThenLogicEndBrick') { + this.isOldMultiPartBrickFormat = true; + } + + let brickId: string | null = null; + const brickIdNode = ParserUtils.getElementByXPath(this.codeXml, 'brickId', brickNode); + if (brickIdNode) { + brickId = ParserUtils.trimTextContent(brickIdNode.textContent); + } + + let userDefinedBrickID: string | null = null; + if (brickType === 'UserDefinedBrick') { + const userBrickIdNode = ParserUtils.getElementByXPath(this.codeXml, 'userDefinedBrickID', brickNode); + if (userBrickIdNode) { + userDefinedBrickID = ParserUtils.trimTextContent(userBrickIdNode.textContent); + brickType = userDefinedBrickID; + } else { + throw new Error('UserDefinedBrick brick without userDefinedBrickID'); + } + } + + const brick = new CatblocksBrick(brickType, brickId); + brick.userDefinedBrickID = userDefinedBrickID; + + brick.commentedOut = false; + const commentedOutNode = ParserUtils.getElementByXPath(this.codeXml, 'commentedOut', brickNode); + if (commentedOutNode) { + if (ParserUtils.trimTextContent(commentedOutNode.textContent).toLowerCase() === 'true') { + brick.commentedOut = true; + } + } + + this.parseFormulas(brickNode, brick); + this.parseSpinners(brickNode, brick); + + ParserUtils.foreachElementByXPath( + this.codeXml, + '*[self::ifBranchBricks or self::loopBricks]/brick', + brickNode, + (subBrickNode: Element) => { + const subBricks = this.parseBrick(subBrickNode); + if (subBricks) { + brick.loopOrIfBrickList.push(subBricks); + } + } + ); + + ParserUtils.foreachElementByXPath( + this.codeXml, + '*[self::elseBranchBricks]/brick', + brickNode, + (subBrickNode: Element) => { + const subBricks = this.parseBrick(subBrickNode); + if (subBricks) { + brick.elseBrickList.push(subBricks); + } + } + ); + return brick; + } + + private parseFormulas(brickNode: Element, brick: CatblocksBrick): void { + ParserUtils.foreachElementByXPath( + this.codeXml, + '*[self::formulaList or self::formulaMap]/formula', + brickNode, + (formulaNode: Element) => { + this.parseSingleFormula(formulaNode, brick); + } + ); + + this.parseSpecialFormulas(brickNode, brick); + } + + private parseSpecialFormulas(brickNode: Element, brick: CatblocksBrick): void { + if (brick.name === 'ParameterizedBrick') { + const formulaList = ParserUtils.getElementByXPath(this.codeXml, 'endBrick/formulaList', brickNode); + if (formulaList) { + const formulaNode = ParserUtils.getElementByXPath(this.codeXml, 'formula', formulaList); + if (formulaNode) { + this.parseSingleFormula(formulaNode, brick); + } + } + + let userListCounter = 0; + ParserUtils.foreachElementByXPath(this.codeXml, 'userLists/userList', brickNode, (formulaNode: Element) => { + if (formulaNode) { + userListCounter++; + } + }); + + let message = + userListCounter === 1 + ? ParserUtils.getMessage(`ASSERTION_PARAMETERIZED_LIST_ONE`, 'list') + : ParserUtils.getMessage(`ASSERTION_PARAMETERIZED_LIST_OTHER`, 'lists'); + message = message.replace('%d', userListCounter.toString()); + brick.formValues.set('CATBLOCKS_ASSERT_LISTS_SELECTED', message); + } + } + + private parseSingleFormula(formulaNode: Element, brick: CatblocksBrick): void { + const formulaCategoryAttr = ParserUtils.getAttrByXPath(this.codeXml, '@category | @input', formulaNode); + if (formulaCategoryAttr) { + const formulaCategory = formulaCategoryAttr.textContent; + if (!formulaCategory) { + throw new Error('Formula category is empty'); + } + const formula = Formula.parse(formulaNode, this.codeXml); + if (formula) { + brick.formValues.set(formulaCategory, formula.toFormulaString()); + } + } + } + + private parseSpinners(brickNode: Element, brick: CatblocksBrick) { + const spinners: Array = getBrickSpinnerProperties(brick.name); + if (spinners) { + for (const spinnerProperties of spinners) { + try { + this.parseSpinner(brickNode, spinnerProperties, brick); + } catch (error) { + throw new Error( + `Spinner value node not found for brick '${brick.name}' spinner '${spinnerProperties.name}': ${error}` + ); + } + } + } + } + + private parseSpinner(brickNode: Element, spinnerProperties: CatblocksSpinnerProperties, brick: CatblocksBrick): void { + const inputName: string = spinnerProperties.name; + const messageFormat: string = spinnerProperties.messageFormat; + + let referenceNode: Node = brickNode; + for (let i = 0; i < spinnerProperties.valueXPath.length; i++) { + const xPath = spinnerProperties.valueXPath[i]; + const valueNode = ParserUtils.getFlatNodeByXPath(this.codeXml, xPath, referenceNode); + if (valueNode) { + if (i < spinnerProperties.valueXPath.length - 1) { + referenceNode = valueNode; + continue; + } + let message: string | null = null; + if (valueNode instanceof Element) { + const value = valueNode.textContent; + message = value; + } else if (valueNode instanceof Attr) { + const value = valueNode.textContent; + if (value) { + message = value; + } + } + if (message !== null && messageFormat !== undefined) { + if (messageFormat) { + message = messageFormat.replace('%v', message); + } + + brick.formValues.set(inputName, ParserUtils.getMessage(message, message)); + break; + } else { + throw new Error(`Spinner value node not found for brick ${brick.name} spinner ${spinnerProperties.name} (1)`); + } + } else { + const message = this.handleSpecialBrickSpinner(brick, spinnerProperties.name, brickNode); + if (message !== null && messageFormat !== undefined) { + brick.formValues.set(inputName, ParserUtils.getMessage(message, message)); + break; + } else { + brick.formValues.set(inputName, ''); + console.warn(`Spinner value node not found for brick ${brick.name} spinner ${spinnerProperties.name} (2)`); + } + } + } + } + + private handleSpecialBrickSpinner(brick: CatblocksBrick, fieldName: string, brickNode: Element): string | null { + if (brick.name === 'CloneBrick') { + return 'CONTROL_OFYOURSELF'; + } + if (brick.name === 'GoToBrick') { + const spinnerSelection = ParserUtils.getElementByXPath(this.codeXml, 'spinnerSelection', brickNode); + if (spinnerSelection) { + const selection = ParserUtils.trimTextContent(spinnerSelection.textContent); + if (selection) { + return `GO_TO_POSITION_${selection}`; + } + } + } + if (brick.name === 'WhenBounceOffScript') { + return 'WHEN_BOUNCE_OFF_ANYTHING'; + } + if (brick.name === 'SetListeningLanguageBrick') { + return ''; + } + return null; + } +} diff --git a/src/common/ts/parser/ParserUtils.ts b/src/common/ts/parser/ParserUtils.ts new file mode 100644 index 00000000..d08f05f3 --- /dev/null +++ b/src/common/ts/parser/ParserUtils.ts @@ -0,0 +1,81 @@ +import { CatBlocksMsgs } from '../../../library/ts/i18n/CatBlocksMsgs'; + +export class ParserUtils { + public static getAttrByXPath(codeXml: Document, xpath: string, contextNode: Node): Attr | null { + const node = ParserUtils.getNodeByXPath(codeXml, xpath, contextNode); + if (node) { + return node; + } + return null; + } + + public static getElementByXPath(codeXml: Document, xpath: string, contextNode: Node): Element | null { + const node = ParserUtils.getNodeByXPath(codeXml, xpath, contextNode); + if (node) { + return ParserUtils.flattenElementReference(codeXml, node); + } + return null; + } + + public static getNodeByXPath(codeXml: Document, xpath: string, contextNode: Node): Node | null { + const iterator = codeXml.evaluate(xpath, contextNode); + const node = iterator?.iterateNext(); + return node; + } + + public static getFlatNodeByXPath(codeXml: Document, xpath: string, contextNode: Node): Node | null { + const iterator = codeXml.evaluate(xpath, contextNode); + const node = iterator?.iterateNext(); + if (node instanceof Element) { + return ParserUtils.flattenElementReference(codeXml, node); + } + return node; + } + + public static foreachElementByXPath( + codeXml: Document, + xpath: string, + contextNode: Node, + nodeCallback: (node: Element) => void + ): void { + const iterator = codeXml.evaluate(xpath, contextNode); + let currentNode: Node | null | undefined = null; + while ((currentNode = iterator?.iterateNext())) { + nodeCallback(ParserUtils.flattenElementReference(codeXml, currentNode)); + } + } + + public static flattenElementReference(codeXml: Document, node: Element): Element { + if (node.nodeType == Node.ELEMENT_NODE) { + const elementNode = node; + if (elementNode.hasAttribute('reference')) { + const referenceXPath = elementNode.getAttribute('reference'); + if (referenceXPath) { + const referencedElement = codeXml?.evaluate(referenceXPath, node).iterateNext(); + if (referencedElement) { + return referencedElement; + } + } + } + } + return node; + } + + public static getMessage(key: string, defaultMessage: string): string { + const message = CatBlocksMsgs.getTranslationForKey(key.toUpperCase()); + if (message) { + return message; + } + if (defaultMessage) { + return defaultMessage; + } + return key; + } + + public static trimTextContent(textContent: string | null | undefined): string { + if (textContent) { + return textContent.trim(); + } + return ''; + } +} diff --git a/src/common/ts/parser/UserDefinedBrick.ts b/src/common/ts/parser/UserDefinedBrick.ts new file mode 100644 index 00000000..813c32d6 --- /dev/null +++ b/src/common/ts/parser/UserDefinedBrick.ts @@ -0,0 +1,66 @@ +import { BlockDefinition } from 'blockly/core/blocks'; +import { UserDefinedBrickInputType } from './UserDefinedBrickInputType'; + +export class UserDefinedBrickDefinition { + userDefinedBrickID: string; + inputTypes: UserDefinedBrickInputType[] = new Array(); + message = ''; + + constructor(userDefinedBrickID: string) { + this.userDefinedBrickID = userDefinedBrickID; + } + + private getArgs(fieldNameEqualsContent: boolean): Partial[] { + const args: Partial[] = []; + for (let i = 0; i < this.inputTypes.length; ++i) { + if (this.inputTypes[i].type.toUpperCase() == 'INPUT') { + args.push({ + type: 'field_catblockstext', + name: this.inputTypes[i].name, + text: fieldNameEqualsContent ? this.inputTypes[i].name : 'unset' + }); + args.push({ + type: 'field_image', + name: `${this.inputTypes[i].name}_INFO`, + src: `${document.location.pathname}media/info_icon.svg`, + height: 24, + width: 24, + alt: '(i)', + flip_rtl: true + }); + } + } + return args; + } + + public getJson(): BlockDefinition { + const args = this.getArgs(false); + return { + message0: this.message, + args0: args, + args2: args, + category: 'user', + colour: '#3556a2', + extensions: ['shapeBrick'] + }; + } + + public getJsonForDefinitionBrick(): BlockDefinition { + const args = this.getArgs(true); + const argValues = new Map(); + for (let i = 0; i < this.inputTypes.length; ++i) { + if (this.inputTypes[i].type.toUpperCase() == 'INPUT') { + argValues.set(this.inputTypes[i].name, this.inputTypes[i].name); + } + } + return { + message0: this.message, + args0: args, + category: 'user', + colour: '#3556a2', + previousStatement: 'userdefinedtemplate', + nextStatement: 'userdefinedtemplate', + formValueMap: argValues + }; + } +} diff --git a/src/common/ts/parser/UserDefinedBrickInputType.ts b/src/common/ts/parser/UserDefinedBrickInputType.ts new file mode 100644 index 00000000..81bfe6a3 --- /dev/null +++ b/src/common/ts/parser/UserDefinedBrickInputType.ts @@ -0,0 +1,9 @@ +export class UserDefinedBrickInputType { + type: string; + name: string; + + constructor(type: string, name: string) { + this.type = type; + this.name = name; + } +} diff --git a/src/intern/js/playground/playground.js b/src/intern/js/playground/playground.js index fddde37b..730a41dc 100644 --- a/src/intern/js/playground/playground.js +++ b/src/intern/js/playground/playground.js @@ -1,6 +1,6 @@ import Blockly from 'blockly'; import { jsonDomToWorkspace, zebraChangeColor, RenderSource_Share } from '../../../library/js/integration/utils'; -import { Parser } from '../../../common/js/parser/parser'; +import { CatblocksParser } from '../../../common/ts/parser/Parser'; import { CatBlocksMsgs } from '../../../library/ts/i18n/CatBlocksMsgs'; import { initBricks } from '../../../library/js/blocks/bricks'; @@ -8,7 +8,6 @@ export class Playground { constructor() { // for debugging this.Blockly = Blockly; - this.Parser = Parser; this.toolbox = undefined; this.workspace = undefined; @@ -255,7 +254,8 @@ export class Playground { try { const input = document.getElementById('importExport'); const xmlString = beforeScript + input.value + afterScript; - const blocksJSON = this.Parser.convertProgramToJSONDebug(xmlString); + const parser = new CatblocksParser(xmlString); + const blocksJSON = parser.xmlToCatblocksProject(); if (blocksJSON !== undefined) { const scenes = blocksJSON.scenes; if (scenes !== undefined && scenes.length > 0) { diff --git a/src/intern/ts/Playground.ts b/src/intern/ts/Playground.ts new file mode 100644 index 00000000..f25f0f32 --- /dev/null +++ b/src/intern/ts/Playground.ts @@ -0,0 +1,31 @@ +import '../scss/style.scss'; + +import { Playground } from '../js/playground/playground'; +import Blockly from 'blockly'; +import { CatBlocksMsgs } from '../../library/ts/i18n/CatBlocksMsgs'; +import { CatBlocksConfig } from '../../library/ts/config/CatBlocksConfig'; +import { ConfigValidator } from '../../library/ts/config/ConfigValidator'; + +declare global { + interface Window { + Catblocks: Playground; + Blockly: typeof Blockly; + } +} + +(async function () { + const config: Partial = { + language: process.env.DISPLAY_LANGUAGE, + rtl: process.env.DISPLAY_RTL?.toLowerCase() === 'true', + container: 'catblocks-workspace-container', + shareRoot: '/' + }; + const validatedConfig = ConfigValidator.parseOptions(config); + + CatBlocksMsgs.init(validatedConfig.i18n); + await CatBlocksMsgs.setLocale('en'); + window.Blockly = Blockly; + const playground = new Playground(); + playground.init(); + window.Catblocks = playground; +})(); diff --git a/src/intern/ts/Testing.ts b/src/intern/ts/Testing.ts index 273e048d..126324ec 100644 --- a/src/intern/ts/Testing.ts +++ b/src/intern/ts/Testing.ts @@ -7,7 +7,7 @@ import { Playground } from '../js/playground/playground'; import Blockly from 'blockly'; import { CatBlocksShare } from '../../library/ts/CatBlocksShare'; import * as shareUtils from '../../library/js/integration/utils'; -import { Parser } from '../../common/js/parser/parser'; +import { CatblocksParser } from '../../common/ts/parser/Parser'; import { CatBlocksCatroid } from '../../library/ts/CatBlocksCatroid'; import { IAndroid } from '../../library/ts/IAndroid'; @@ -60,7 +60,7 @@ class Test { CatBlocksMsgs: typeof CatBlocksMsgs; CatBlocks: typeof CatBlocksShare; ShareUtils: typeof shareUtils; - Parser: typeof Parser; + Parser: CatblocksParser; CatroidCatBlocks: typeof CatBlocksCatroid; constructor() { @@ -69,8 +69,8 @@ class Test { this.CatBlocksMsgs = CatBlocksMsgs; this.CatBlocks = CatBlocksShare; this.ShareUtils = shareUtils; - this.Parser = Parser; this.CatroidCatBlocks = CatBlocksCatroid; + this.Parser = new CatblocksParser(); } } diff --git a/src/intern/ts/render/FileHandlerBase.ts b/src/intern/ts/render/FileHandlerBase.ts index fb245996..8678cee6 100644 --- a/src/intern/ts/render/FileHandlerBase.ts +++ b/src/intern/ts/render/FileHandlerBase.ts @@ -1,8 +1,8 @@ import JSZip from 'jszip'; import { MessageBox } from './MessageBox'; -import { Parser } from '../../../common/js/parser/parser'; import { CatBlocksShare } from '../../../library/ts/CatBlocksShare'; import { generateNewDOM } from '../../../library/js/integration/utils'; +import { CatblocksParser } from '../../../common/ts/parser/Parser'; enum FileType { IMAGE, @@ -202,8 +202,8 @@ class FileHandlerBase { counter: number, fileMap: Record ) { - // inject code - const programJSON = Parser.convertProgramToJSONDebug(codeXML); + const parser = new CatblocksParser(codeXML); + const projectJson = parser.xmlToCatblocksProject(); // prepare container for program injection const programContainer = this.createProgramContainer(container, name, counter); @@ -212,7 +212,7 @@ class FileHandlerBase { CatBlocksShare.controller.renderProgramJSON( programID, programContainer, - programJSON, + projectJson, { object: { fileMap: fileMap diff --git a/src/library/js/blocks/bricks.js b/src/library/js/blocks/bricks.js index 6f12d84d..743e5d9a 100644 --- a/src/library/js/blocks/bricks.js +++ b/src/library/js/blocks/bricks.js @@ -5,6 +5,7 @@ import Blockly from 'blockly'; import categories from './categories'; import { initCatblocksColours } from './colours'; +import { CatblocksSpinnerProperties } from '../../../common/ts/parser/CatblocksSpinnerProperties'; export const getBrickScriptMapping = () => { return new Map() @@ -116,6 +117,41 @@ const loadBricks = (cats = categories, blockly = Blockly, advancedMode = false) } }; +function getAllBricks() { + const bricks = {}; + for (const catName in categories) { + const cat = categories[catName]; + for (const brickName in cat) { + bricks[`${brickName}`] = cat[brickName]; + } + } + return bricks; +} + +export function getBrickSpinnerProperties(brickType) { + const allBricks = getAllBricks(); + const spinners = []; + + if (Object.prototype.hasOwnProperty.call(allBricks, brickType)) { + const brick = allBricks[brickType]; + for (let i = 0; i < 5; i++) { + const argName = `args${i}`; + if (Object.prototype.hasOwnProperty.call(brick, argName)) { + const args = brick[argName]; + for (const arg of args) { + if (arg['type'] === 'field_catblocksspinner') { + const name = arg['name']; + const valueXpaths = arg['value_xpath']; + const messageFormat = arg['message_format']; + spinners.push(new CatblocksSpinnerProperties(name, valueXpaths, messageFormat)); + } + } + } + } + } + return spinners; +} + /** * @param {boolean} advancedMode */ diff --git a/src/library/js/blocks/categories/control.js b/src/library/js/blocks/categories/control.js index c59fdec0..590131a0 100644 --- a/src/library/js/blocks/categories/control.js +++ b/src/library/js/blocks/categories/control.js @@ -288,6 +288,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_scene_transition_spinner', + value_xpath: ['sceneForTransition'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -307,6 +309,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_scene_start_spinner', + value_xpath: ['sceneToStart'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -326,6 +330,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_stop_script_spinner', + value_xpath: ['spinnerSelection'], + message_format: 'STOP_SCRIPT_%v', name: 'SPINNER' }, { @@ -345,6 +351,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_clone_spinner', + value_xpath: ['objectToClone', '@name'], + message_format: '%v', name: 'SPINNER' }, { @@ -404,6 +412,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_for_variable_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -450,6 +460,8 @@ export default { { type: 'field_catblocksspinner', name: 'FOR_ITEM_IN_USERLIST_LIST', + value_xpath: ['userDataList', 'userData[@category="FOR_ITEM_IN_USERLIST_LIST"]', 'name'], + message_format: '%v', catroid_field_id: 'for_item_in_userlist_list_spinner' }, { @@ -464,6 +476,14 @@ export default { { type: 'field_catblocksspinner', name: 'FOR_ITEM_IN_USERLIST_VARIABLE', + value_xpath: [ + 'userDataList', + 'userData[@category="FOR_ITEM_IN_USERLIST_VARIABLE"]', + 'userVariable', + 'default', + 'name' + ], + message_format: '%v', catroid_field_id: 'for_item_in_userlist_variable_spinner' }, { @@ -507,6 +527,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_broadcast_spinner', + value_xpath: ['broadcastMessage'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -526,6 +548,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_broadcast_spinner', + value_xpath: ['broadcastMessage'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -606,6 +630,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_phiro_sensor_action_spinner', + value_xpath: ['sensorSpinnerPosition'], + message_format: 'SPINNER_PHIRO_%v', name: 'DROPDOWN' }, { diff --git a/src/library/js/blocks/categories/device.js b/src/library/js/blocks/categories/device.js index d04225e4..4ad8ca63 100644 --- a/src/library/js/blocks/categories/device.js +++ b/src/library/js/blocks/categories/device.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_when_nfc_spinner', + value_xpath: ['nfcTag', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -40,6 +42,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_nfc_tag_ndef_record_spinner', + value_xpath: ['nfcTagNdefType'], + message_format: 'TNF_%v', name: 'DROPDOWN' }, { diff --git a/src/library/js/blocks/categories/event.js b/src/library/js/blocks/categories/event.js index 93834866..c7a74349 100644 --- a/src/library/js/blocks/categories/event.js +++ b/src/library/js/blocks/categories/event.js @@ -19,6 +19,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_broadcast_spinner', + value_xpath: ['receivedMessage'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -57,6 +59,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_when_background_spinner', + value_xpath: ['look', '@name'], + message_format: '%v', name: 'look' }, { diff --git a/src/library/js/blocks/categories/lego.js b/src/library/js/blocks/categories/lego.js index 3bfa0c2d..f36ed4cc 100644 --- a/src/library/js/blocks/categories/lego.js +++ b/src/library/js/blocks/categories/lego.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'lego_ev3_motor_turn_angle_spinner', + value_xpath: ['motor'], + message_format: 'EV3_%v', name: 'DROPDOWN' }, { @@ -40,6 +42,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_ev3_motor_move_spinner', + value_xpath: ['motor'], + message_format: 'EV3_%v', name: 'DROPDOWN' }, { @@ -73,6 +77,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'ev3_stop_motor_spinner', + value_xpath: ['motor'], + message_format: 'EV3_%v', name: 'DROPDOWN' }, { @@ -139,6 +145,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_ev3_set_led_spinner', + value_xpath: ['ledStatus'], + message_format: 'EV3_%v', name: 'DROPDOWN' }, { @@ -158,6 +166,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'lego_motor_turn_angle_spinner', + value_xpath: ['motor'], + message_format: 'NXT_%v', name: 'DROPDOWN' }, { @@ -191,6 +201,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'stop_motor_spinner', + value_xpath: ['motor'], + message_format: 'NXT_%v', name: 'DROPDOWN' }, { @@ -210,6 +222,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'lego_motor_action_spinner', + value_xpath: ['motor'], + message_format: 'NXT_%v', name: 'DROPDOWN' }, { diff --git a/src/library/js/blocks/categories/look.js b/src/library/js/blocks/categories/look.js index 92a962ad..8aeb129a 100644 --- a/src/library/js/blocks/categories/look.js +++ b/src/library/js/blocks/categories/look.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_look_spinner', + value_xpath: ['look', '@name'], + message_format: '%v', name: 'look' }, { @@ -90,6 +92,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_ask_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -349,6 +353,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_background_spinner', + value_xpath: ['look', '@name'], + message_format: '%v', name: 'look' }, { @@ -387,6 +393,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_background_spinner', + value_xpath: ['look', '@name'], + message_format: '%v', name: 'look' }, { @@ -425,6 +433,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_video_spinner', + value_xpath: ['spinnerSelectionON'], + message_format: 'CAMSPINNER_%v', name: 'SPINNER' }, { @@ -444,6 +454,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_choose_camera_spinner', + value_xpath: ['spinnerSelectionFRONT'], + message_format: 'CAMCHOOSESPINNER_%v', name: 'SPINNER' }, { @@ -463,6 +475,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_flash_spinner', + value_xpath: ['spinnerSelectionID'], + message_format: 'FLASHSPINNER_%v', name: 'SPINNER' }, { @@ -567,6 +581,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_phiro_rgb_light_spinner', + value_xpath: ['eye'], + message_format: 'PHIRO_EYE_%v', name: 'eye' }, { @@ -627,6 +643,8 @@ export default { args0: [ { type: 'field_catblocksspinner', + value_xpath: ['fadeSpinnerSelectionId'], + message_format: 'FADESPINNER_%v', name: 'brick_fade_particle_effect_spinner' }, { @@ -645,6 +663,8 @@ export default { args0: [ { type: 'field_catblocksspinner', + value_xpath: ['fadeSpinnerSelectionId'], + message_format: 'PARTICLESPINNER_%v', name: 'brick_additive_particle_effect_spinner' }, { diff --git a/src/library/js/blocks/categories/motion.js b/src/library/js/blocks/categories/motion.js index 9d771352..a0f7f73d 100644 --- a/src/library/js/blocks/categories/motion.js +++ b/src/library/js/blocks/categories/motion.js @@ -116,6 +116,8 @@ export default { { type: 'field_catblocksspinner', name: 'SPINNER', + value_xpath: ['destinationSprite', '@name'], + message_format: '%v', catroid_field_id: 'brick_go_to_spinner' }, { @@ -214,6 +216,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_point_to_spinner', + value_xpath: ['pointedObject', '@name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -233,6 +237,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_rotation_style_spinner', + value_xpath: ['selection'], + message_format: 'POINTTO_%v', name: 'SPINNER' }, { @@ -340,6 +346,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_physics_object_type_spinner', + value_xpath: ['type'], + message_format: 'GRAVITY_%v', name: 'DROPDOWN' }, { @@ -793,6 +801,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_drone_play_led_animation_spinner', + value_xpath: ['ledAnimationName'], + message_format: '%v', name: 'ADRONEANIMATION', text: 'unset' }, @@ -813,6 +823,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_jumping_sumo_animation_spinner', + value_xpath: ['animationName'], + message_format: 'ANIMATION_%v', name: 'ANIMATION' }, { @@ -948,6 +960,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_when_bounce_off_spinner', + value_xpath: ['spriteToBounceOffName'], // empty if all + message_format: '%v', name: 'DROPDOWN' }, { diff --git a/src/library/js/blocks/categories/phiro.js b/src/library/js/blocks/categories/phiro.js index d79388ce..43e2851b 100644 --- a/src/library/js/blocks/categories/phiro.js +++ b/src/library/js/blocks/categories/phiro.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_phiro_motor_forward_action_spinner', + value_xpath: ['motor'], + message_format: 'PHIRO_%v', name: 'DROPDOWN' }, { @@ -40,6 +42,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_phiro_motor_backward_action_spinner', + value_xpath: ['motor'], + message_format: 'PHIRO_%v', name: 'DROPDOWN' }, { @@ -73,6 +77,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_phiro_stop_motor_spinner', + value_xpath: ['motor'], + message_format: 'PHIRO_%v', name: 'DROPDOWN' }, { diff --git a/src/library/js/blocks/categories/script.js b/src/library/js/blocks/categories/script.js index 088b1c5e..d3cbb380 100644 --- a/src/library/js/blocks/categories/script.js +++ b/src/library/js/blocks/categories/script.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_when_gamepad_button_spinner', + value_xpath: ['action'], + message_format: '%v', name: 'ACTION' }, { @@ -26,6 +28,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_raspi_when_pinspinner', + value_xpath: ['pin'], + message_format: '%v', name: 'pin' }, { @@ -40,6 +44,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_raspi_when_valuespinner', + value_xpath: ['eventValue'], + message_format: 'RASPI_%v', name: 'eventValue' }, { diff --git a/src/library/js/blocks/categories/sound.js b/src/library/js/blocks/categories/sound.js index 3884d4f1..94ca7642 100644 --- a/src/library/js/blocks/categories/sound.js +++ b/src/library/js/blocks/categories/sound.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_play_sound_spinner', + value_xpath: ['sound', '@name'], + message_format: '%v', name: 'sound' }, { @@ -26,6 +28,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_play_sound_spinner', + value_xpath: ['sound', '@name'], + message_format: '%v', name: 'sound' }, { @@ -138,6 +142,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_ask_speech_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -157,6 +163,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_stop_sound_spinner', + value_xpath: ['sound', '@name'], + message_format: '%v', name: 'sound' }, { @@ -176,6 +184,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_start_listening_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'LISTEN' }, { @@ -195,6 +205,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'set_instrument_spinner', + value_xpath: ['instrumentSelection'], + message_format: 'INSTRUMENT_%v', name: 'DROPDOWN' }, { @@ -233,6 +245,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_listening_language_spinner', + value_xpath: ['languageObject'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -290,6 +304,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'play_drum_for_beats_spinner', + value_xpath: ['drumSelection'], + message_format: 'DRUM_%v', name: 'DROPDOWN' }, { @@ -356,6 +372,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_jumping_sumo_sound_spinner', + value_xpath: ['soundName'], + message_format: 'JUMPING_SUMO_SOUND_%v', name: 'DROPDOWN' }, { @@ -392,6 +410,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_phiro_select_tone_spinner', + value_xpath: ['tone'], + message_format: 'PHIRO_TONE_%v', name: 'tone' }, { @@ -426,6 +446,8 @@ export default { type: 'field_catblocksspinner', catroid_field_id: 'brick_play_sound_at_spinner', name: 'sound', + value_xpath: ['sound', '@name'], + message_format: '%v', text: 'unset' }, { diff --git a/src/library/js/blocks/categories/test.js b/src/library/js/blocks/categories/test.js index b32bb98b..4b739a72 100644 --- a/src/library/js/blocks/categories/test.js +++ b/src/library/js/blocks/categories/test.js @@ -43,6 +43,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_assert_lists_actual', + value_xpath: ['userDataList', 'userData[@category="ASSERT_LISTS_ACTUAL"]', 'name'], + message_format: '%v', name: 'ASSERT_LISTS_ACTUAL' }, { @@ -57,6 +59,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_assert_lists_expected', + value_xpath: ['userDataList', 'userData[@category="ASSERT_LISTS_EXPECTED"]', 'name'], + message_format: '%v', name: 'ASSERT_LISTS_EXPECTED' }, { @@ -114,6 +118,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_param_expected_list', + value_xpath: ['endBrick', 'userList', 'name'], + message_format: '%v', name: 'LIST_SELECTED' }, { diff --git a/src/library/js/blocks/categories/user.js b/src/library/js/blocks/categories/user.js index a64a6062..c3045a67 100644 --- a/src/library/js/blocks/categories/user.js +++ b/src/library/js/blocks/categories/user.js @@ -24,6 +24,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_set_screen_refresh_spinner', + value_xpath: ['screenRefresh'], + message_format: 'UDB_SCREEN_REFRESH_%v', name: 'UDB_SCREEN_REFRESH' }, { diff --git a/src/library/js/blocks/categories/userlist.js b/src/library/js/blocks/categories/userlist.js index 59dc3139..253c5cfc 100644 --- a/src/library/js/blocks/categories/userlist.js +++ b/src/library/js/blocks/categories/userlist.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'read_list_from_device_spinner', + value_xpath: ['userList', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -26,6 +28,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'write_list_spinner', + value_xpath: ['userList', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { diff --git a/src/library/js/blocks/categories/uservariables.js b/src/library/js/blocks/categories/uservariables.js index d81e92df..4b1e5b4c 100644 --- a/src/library/js/blocks/categories/uservariables.js +++ b/src/library/js/blocks/categories/uservariables.js @@ -7,6 +7,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'set_variable_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -40,6 +42,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'change_variable_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -73,6 +77,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'show_variable_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN', text: 'unset' }, @@ -121,6 +127,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'show_variable_color_size_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -191,6 +199,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_show_variable_color_size_align_spinner', + value_xpath: ['alignmentSelection'], + message_format: 'ALIGNMENTS_%v', name: 'ALIGNMENT' }, { @@ -210,6 +220,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'delete_item_of_userlist_spinner', + value_xpath: ['userList', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -256,7 +268,10 @@ export default { }, { type: 'field_catblocksspinner', + value_xpath: ['userList', 'name'], + message_format: '%v', catroid_field_id: 'add_item_to_userlist_spinner', + name: 'DROPDOWN' }, { @@ -290,6 +305,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'insert_item_into_userlist_spinner', + value_xpath: ['userList', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -323,6 +340,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'replace_item_in_userlist_spinner', + value_xpath: ['userList', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -370,6 +389,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'hide_variable_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -390,6 +411,8 @@ export default { type: 'field_catblocksspinner', catroid_field_id: 'read_variable_from_device_spinner', name: 'DROPDOWN', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', text: 'unset' }, { @@ -410,6 +433,8 @@ export default { type: 'field_catblocksspinner', catroid_field_id: 'write_variable_spinner', name: 'DROPDOWN', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', text: 'unset' }, { @@ -457,6 +482,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_store_csv_into_userlist_spinner', + value_xpath: ['userList', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -476,6 +503,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'clear_userlist_spinner', + value_xpath: ['userList', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -510,6 +539,8 @@ export default { type: 'field_catblocksspinner', catroid_field_id: 'web_request_spinner', name: 'DROPDOWN', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', text: 'unset' }, { @@ -529,6 +560,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_read_variable_from_file_spinner_variable', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { @@ -557,6 +590,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_read_variable_from_file_spinner_mode', + value_xpath: ['spinnerSelectionID'], + message_format: 'READ_VARIABLE_%v', name: 'SPINNER' }, { @@ -576,6 +611,8 @@ export default { { type: 'field_catblocksspinner', catroid_field_id: 'brick_write_variable_to_file_spinner', + value_xpath: ['userVariable', 'userVariable', 'default', 'name'], + message_format: '%v', name: 'DROPDOWN' }, { diff --git a/src/library/js/integration/catroid.js b/src/library/js/integration/catroid.js index 8872c710..e76fcd12 100644 --- a/src/library/js/integration/catroid.js +++ b/src/library/js/integration/catroid.js @@ -3,7 +3,7 @@ import '../../scss/catroid.scss'; import { getBrickScriptMapping } from '../blocks/bricks'; import Blockly from 'blockly'; -import { Parser } from '../../../common/js/parser/parser'; +import { CatblocksParser } from '../../../common/ts/parser/Parser'; import { defaultOptions, generateFormulaModal, @@ -86,7 +86,8 @@ export class Catroid { const newId = Android.duplicateBrick(scope.block.id); const codeXML = Android.getCurrentProject(); - const objectJSON = Parser.convertObjectToJSON(codeXML, this.scene, this.object); + const parser = new CatblocksParser(codeXML); + const objectJSON = parser.xmlToCatblocksObject(this.scene, this.object); const clone = objectJSON.scriptList.filter(x => x.id.toLowerCase() == newId.toLowerCase()); if (clone && clone.length) { @@ -455,7 +456,8 @@ export class Catroid { const newScriptId = bricksToAdd[0].brickId.toLowerCase(); const codeXML = Android.getCurrentProject(); - const objectJSON = Parser.convertObjectToJSON(codeXML, this.scene, this.object); + const parser = new CatblocksParser(codeXML); + const objectJSON = parser.xmlToCatblocksObject(this.scene, this.object); const newScript = objectJSON.scriptList.filter(x => x.id.toLowerCase() == newScriptId); if (newScript && newScript.length) { diff --git a/src/library/js/integration/share.js b/src/library/js/integration/share.js index 150dea1b..eb0e6e29 100644 --- a/src/library/js/integration/share.js +++ b/src/library/js/integration/share.js @@ -431,11 +431,11 @@ export class Share { if (object.lookList) { for (const look of object.lookList) { - if (!options.sceneName || !look.fileName) { + if (!options.sceneName || !look.name) { continue; } - const imgPath = `${options.sceneName}/images/${look.fileName}`; + const imgPath = `${options.sceneName}/images/${look.path}`; src = escapeURI(`${this.config.shareRoot}${options.programRoot}${imgPath}`); if (options.programRoot.startsWith('http')) { @@ -568,12 +568,12 @@ export class Share { class: 'col' }); - if (!options.sceneName || !sound.fileName) { + if (!options.sceneName || !sound.name) { failed++; continue; } - const soundPath = `${options.sceneName}/sounds/${sound.fileName}`; + const soundPath = `${options.sceneName}/sounds/${sound.path}`; let src = escapeURI(`${this.config.shareRoot}${options.programRoot}${soundPath}`); if (options.programRoot.startsWith('http')) { @@ -586,7 +586,7 @@ export class Share { let displaySoundName = sound.name; if (!displaySoundName) { - displaySoundName = sound.fileName; + displaySoundName = sound.name; } const audioContainer = generateNewDOM(col, 'audio', { @@ -670,12 +670,12 @@ export class Share { class: 'col-12 col-sm-6 d-flex align-items-center' }); - if (!options.sceneName || !look.fileName) { + if (!options.sceneName || !look.name) { failed++; continue; } - const imgPath = `${options.sceneName}/images/${look.fileName}`; + const imgPath = `${options.sceneName}/images/${look.path}`; let src = escapeURI(`${this.config.shareRoot}${options.programRoot}${imgPath}`); // renderProgram got a full link @@ -693,7 +693,7 @@ export class Share { let displayLookName = look.name; if (!displayLookName) { - displayLookName = look.fileName; + displayLookName = look.name; } const imgID = generateID(`${objectID}-${displayLookName}`) + '-imgID'; diff --git a/src/library/js/integration/utils.js b/src/library/js/integration/utils.js index be4394a1..8ff60992 100644 --- a/src/library/js/integration/utils.js +++ b/src/library/js/integration/utils.js @@ -306,10 +306,8 @@ export const renderAndConnectBlocksInList = (parentBrick, brickList, brickListTy } } - if (parentBrick === null && brickList[i].userBrickId !== undefined) { - // When there is no parentBrick but the userBrickId is set - // ChildBrick is a UserDefinedScript and we need to add the UserDefinedBrick definition - const definitionBrickName = brickList[i].userBrickId + '_UDB_CATBLOCKS_DEF'; + if (brickList[i].name === 'UserDefinedScript') { + const definitionBrickName = brickList[i].userDefinedBrickID + '_UDB_CATBLOCKS_DEF'; const definitionBrick = Blockly.Bricks[definitionBrickName]; const definitionBrickToRender = { name: definitionBrickName, @@ -322,7 +320,7 @@ export const renderAndConnectBlocksInList = (parentBrick, brickList, brickListTy } if (brickList[i].brickList !== undefined && brickList[i].brickList.length > 0) { - if (brickList[i].userBrickId !== undefined) { + if (brickList[i].userDefinedBrickID !== undefined) { // if there are bricks in the brickList and the userBrickId is set, it is a UserDefinedScript renderAndConnectBlocksInList( childBrick, @@ -381,9 +379,7 @@ export const renderBrick = (parentBrick, jsonBrick, brickListType, workspace, re let catblocksDomBrickID; const brickIDGenerator = new BrickIDGenerator(); if (childBrick.type === 'UserDefinedScript') { - catblocksDomBrickID = brickIDGenerator.createBrickIDForUserDefinedScript(childBrick, jsonBrick.userBrickId); - } else if (childBrick.type !== 'UserDefinedScript' && jsonBrick.userBrickId) { - catblocksDomBrickID = brickIDGenerator.createBrickIDForUserDefinedScriptCall(childBrick, jsonBrick.userBrickId); + catblocksDomBrickID = brickIDGenerator.createBrickIDForUserDefinedScript(childBrick, jsonBrick.userDefinedBrickID); } else { catblocksDomBrickID = brickIDGenerator.createBrickID(childBrick); } @@ -625,13 +621,13 @@ export const lazyLoadImage = (event, eventRoot, callback) => { export const buildUserDefinedBrick = (object, advancedMode = false) => { const createdBricks = []; - if (!object.userBricks) { + if (!object.userDefinedBricks) { return createdBricks; } - for (let i = 0; i < object.userBricks.length; ++i) { - const jsonDef = object.userBricks[i].getJsonDefinition(); - const brickName = object.userBricks[i].id; + for (let i = 0; i < object.userDefinedBricks.length; ++i) { + const jsonDef = object.userDefinedBricks[i].getJson(); + const brickName = object.userDefinedBricks[i].userDefinedBrickID; Blockly.Bricks[brickName] = jsonDef; Blockly.Blocks[brickName] = { init: function () { @@ -645,8 +641,8 @@ export const buildUserDefinedBrick = (object, advancedMode = false) => { }; createdBricks.push(brickName); - const definitionJsonDef = object.userBricks[i].getDefinitionJsonDefinition(); - const definitionBrickName = object.userBricks[i].id + '_UDB_CATBLOCKS_DEF'; + const definitionJsonDef = object.userDefinedBricks[i].getJsonForDefinitionBrick(); + const definitionBrickName = object.userDefinedBricks[i].userDefinedBrickID + '_UDB_CATBLOCKS_DEF'; Blockly.Bricks[definitionBrickName] = definitionJsonDef; Blockly.Blocks[definitionBrickName] = { init: function () { diff --git a/src/library/ts/CatBlocksCatroid.ts b/src/library/ts/CatBlocksCatroid.ts index d1120219..ed71a0cd 100644 --- a/src/library/ts/CatBlocksCatroid.ts +++ b/src/library/ts/CatBlocksCatroid.ts @@ -1,5 +1,5 @@ import { Modal } from 'bootstrap'; -import { Parser } from '../../common/js/parser/parser'; +import { CatblocksParser } from '../../common/ts/parser/Parser'; import { CatBlocksConfig } from './config/CatBlocksConfig'; import { Catroid } from '../js/integration/catroid'; import { CatBlocksBase } from './CatBlocksBase'; @@ -24,6 +24,10 @@ export class CatBlocksCatroid extends CatBlocksBase { } public static render(codeXML: string, showScene?: string, showObject?: string, brickIDToFocus?: string) { + if (!showObject || !showScene) { + throw new Error('Invalid object or scene. Object and scene must be selected'); + } + return new Promise((resolve, reject) => { const spinnerElement = document.getElementById('spinnerModal'); if (!spinnerElement) { @@ -35,7 +39,8 @@ export class CatBlocksCatroid extends CatBlocksBase { const eventListener = () => { spinnerElement.removeEventListener('shown.bs.modal', eventListener, false); try { - const objectJSON = Parser.convertObjectToJSON(codeXML, showScene, showObject); + const parser = new CatblocksParser(codeXML); + const objectJSON = parser.xmlToCatblocksObject(showScene, showObject); console.log(objectJSON); this.controller.scene = showScene; this.controller.object = showObject; diff --git a/src/library/ts/CatBlocksShare.ts b/src/library/ts/CatBlocksShare.ts index 60594172..03bb277d 100644 --- a/src/library/ts/CatBlocksShare.ts +++ b/src/library/ts/CatBlocksShare.ts @@ -1,4 +1,4 @@ -import { Parser } from '../../common/js/parser/parser'; +import { CatblocksParser } from '../../common/ts/parser/Parser'; import { CatBlocksConfig } from './config/CatBlocksConfig'; import { Share } from '../js/integration/share'; import { CatBlocksBase } from './CatBlocksBase'; @@ -51,7 +51,8 @@ export class CatBlocksShare extends CatBlocksBase { const response = await fetch(`${path}${name}/code.xml`); const codeXML = await response.text(); - const programJSON = Parser.convertProgramToJSONDebug(codeXML); + const parser = new CatblocksParser(codeXML); + const programJSON = parser.xmlToCatblocksProject(); const programID = `catblocks-program-${name}`; diff --git a/test/jsunit/block/block.test.js b/test/jsunit/block/block.test.js index e49b037a..c9f724e3 100644 --- a/test/jsunit/block/block.test.js +++ b/test/jsunit/block/block.test.js @@ -537,13 +537,13 @@ describe('Catroid Block IDs', () => { }; const firstQueryBase = '#UserDefinedScript-0 #IfLogicBeginBrick-0 '; - await checkQuerySelectorExistence(firstQueryBase + '#UserDefinedScript-0-Call-0'); - await checkQuerySelectorExistence(firstQueryBase + '#UserDefinedScript-0-Call-1'); - await checkQuerySelectorExistence(firstQueryBase + '#UserDefinedScript-1-Call-0'); + await checkQuerySelectorExistence(firstQueryBase + '#e8535f0b-477f-42da-b2cf-ed1168eafda9-0'); + await checkQuerySelectorExistence(firstQueryBase + '#e8535f0b-477f-42da-b2cf-ed1168eafda9-1'); + await checkQuerySelectorExistence(firstQueryBase + '#f3bbf4a4-15bf-4eea-b622-82548f172ade-0'); const secondQueryBase = '#UserDefinedScript-1 #IfLogicBeginBrick-1 '; - await checkQuerySelectorExistence(secondQueryBase + '#UserDefinedScript-1-Call-1'); - await checkQuerySelectorExistence(secondQueryBase + '#UserDefinedScript-1-Call-2'); - await checkQuerySelectorExistence(secondQueryBase + '#UserDefinedScript-0-Call-2'); + await checkQuerySelectorExistence(secondQueryBase + '#f3bbf4a4-15bf-4eea-b622-82548f172ade-1'); + await checkQuerySelectorExistence(secondQueryBase + '#f3bbf4a4-15bf-4eea-b622-82548f172ade-2'); + await checkQuerySelectorExistence(secondQueryBase + '#e8535f0b-477f-42da-b2cf-ed1168eafda9-2'); }, 99999); }); diff --git a/test/jsunit/parser/parser.test.js b/test/jsunit/parser/parser.test.js index 5e23ed5f..e5285a52 100644 --- a/test/jsunit/parser/parser.test.js +++ b/test/jsunit/parser/parser.test.js @@ -55,7 +55,8 @@ describe('Parser catroid program tests', () => { const result = await page.evaluate(pXML => { try { - Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + Test.Parser.xmlToCatblocksProject(); } catch (e) { return e.message; } @@ -80,7 +81,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -106,7 +108,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -133,7 +136,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -170,7 +174,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -214,7 +219,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -264,7 +270,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -324,7 +331,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -382,7 +390,8 @@ describe('Parser catroid program tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -455,12 +464,13 @@ describe('Catroid to Catblocks parser tests', () => { `; const formula = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; return formulaMap.entries().next().value.toString(); }, xmlString); - expect(formula).toMatch('SIZE, 60& '); + expect(formula).toMatch('SIZE,60&.0'); }); test('LookList reference not within the same object', async () => { @@ -505,7 +515,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -517,7 +528,7 @@ describe('Catroid to Catblocks parser tests', () => { lookList: expect.arrayContaining([ expect.objectContaining({ name: lookName, - fileName: lookFileName + path: lookFileName }) ]) }) @@ -570,7 +581,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -583,7 +595,7 @@ describe('Catroid to Catblocks parser tests', () => { soundList: expect.arrayContaining([ expect.objectContaining({ name: soundName, - fileName: soundFileName + path: soundFileName }) ]) }) @@ -624,7 +636,7 @@ describe('Catroid to Catblocks parser tests', () => { false - + NUMBER ${val1} @@ -649,7 +661,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, formulaString] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); return [ programJSON, programJSON.scenes[0].objectList[1].scriptList[0].brickList[0].formValues.entries().next().value.toString() @@ -657,7 +670,7 @@ describe('Catroid to Catblocks parser tests', () => { }, xmlString); expect(programJSON.scenes[0].objectList[1].scriptList[0].brickList[0].name).toBe(brickName); - expect(formulaString).toMatch(`${val1} ${val2}`); + expect(formulaString).toMatch(`${val1} ${val2}`); }); test('parser converts catroid script properly', async () => { @@ -703,7 +716,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -773,7 +787,7 @@ describe('Catroid to Catblocks parser tests', () => { - false + false false @@ -787,7 +801,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const programJSON = await page.evaluate(pXML => { - return Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + return Test.Parser.xmlToCatblocksProject(); }, xmlString); expect(programJSON).toEqual( @@ -862,7 +877,8 @@ describe('Catroid to Catblocks parser tests', () => { const langObj = JSON.parse(utils.readFileSync(`${utils.PATHS.CATBLOCKS_MSGS}${lang}.json`)); const [programJSON, formulaString] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); return [ programJSON, programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues.entries().next().value.toString() @@ -910,7 +926,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, formulaSize, formulaString] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); return [ programJSON, programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues.size, @@ -976,7 +993,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, formula] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); return [ programJSON, programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues.entries().next().value @@ -1005,7 +1023,8 @@ describe('Catroid to Catblocks parser tests', () => { }) ); - expect(formula).toBeNull(); + expect(formula[0]).toBe('ADRONEANIMATION'); + expect(formula[1]).toBe(''); }); }); @@ -1061,7 +1080,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; const mapKeys = []; @@ -1097,7 +1117,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([variableName, 'DROPDOWN']); - expect(mapValues).toEqual([' ' + firstValue + ' ', userVarialbe]); + expect(mapValues).toEqual([firstValue, userVarialbe]); }); test('local empty name uservariable parsing', async () => { @@ -1150,7 +1170,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; const mapKeys = []; @@ -1186,7 +1207,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([variableName, 'DROPDOWN']); - expect(mapValues).toEqual([' ' + firstValue + ' ', '']); + expect(mapValues).toEqual([firstValue, '']); }); test('local uservariable parsing without name tag', async () => { @@ -1239,7 +1260,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; const mapKeys = []; @@ -1275,7 +1297,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([variableName, 'DROPDOWN']); - expect(mapValues).toEqual([' ' + firstValue + ' ', '']); + expect(mapValues).toEqual([firstValue, '']); }); test('remote uservariable parsing', async () => { @@ -1339,7 +1361,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -1376,7 +1399,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([variableName, 'DROPDOWN']); - expect(mapValues).toEqual([' ' + firstValue + ' ', userVariable]); + expect(mapValues).toEqual([firstValue, userVariable]); }); test('remote empty name uservariable parsing', async () => { @@ -1439,7 +1462,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -1476,7 +1500,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([variableName, 'DROPDOWN']); - expect(mapValues).toEqual([' ' + firstValue + ' ', '']); + expect(mapValues).toEqual([firstValue, '']); }); test('remote uservariable parsing without name tag', async () => { @@ -1539,7 +1563,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -1576,7 +1601,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([variableName, 'DROPDOWN']); - expect(mapValues).toEqual([' ' + firstValue + ' ', '']); + expect(mapValues).toEqual([firstValue, '']); }); test('parser handles formula operator properly', async () => { @@ -1630,7 +1655,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -1666,8 +1692,8 @@ describe('Catroid to Catblocks parser tests', () => { }) ); - expect(mapKeys).toEqual([variableName]); - expect(mapValues).toEqual([` ${firstValue} = ${secondValue} `]); + expect(mapKeys).toEqual([variableName, 'DROPDOWN']); + expect(mapValues).toEqual([`${firstValue} = ${secondValue}`, '']); }); }); @@ -1747,7 +1773,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -1784,7 +1811,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([` ${first} × ( ${second} ÷ ( ${third} + ${fourth} ))`]); + expect(mapValues).toEqual([`${first} × (${second} ÷ (${third} + ${fourth}))`]); }); test('Formula with left sided brackets', async () => { @@ -1862,7 +1889,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -1899,7 +1927,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([`(( ${first} + ${second} ) × ${third} ) ÷ ${fourth} `]); + expect(mapValues).toEqual([`((${first} + ${second}) × ${third}) ÷ ${fourth}`]); }); test('Formula with both sided brackets', async () => { @@ -1977,7 +2005,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2014,7 +2043,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([`( ${first} × ${second} ) + ( ${third} × ${fourth} )`]); + expect(mapValues).toEqual([`(${first} × ${second}) + (${third} × ${fourth})`]); }); }); @@ -2074,7 +2103,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2111,7 +2141,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([`square root( ${first} ) × ${second} `]); + expect(mapValues).toEqual([`square root(${first}) × ${second}`]); }); test('Single value like sin function with logic', async () => { @@ -2169,7 +2199,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2206,7 +2237,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([`sine( ${first} ) > ${second} `]); + expect(mapValues).toEqual([`sine(${first}) > ${second}`]); }); test('Two single values like sin plus cos', async () => { @@ -2268,7 +2299,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2305,7 +2337,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([`cosine( ${first} ) + sine( ${second} )`]); + expect(mapValues).toEqual([`cosine(${first}) + sine(${second})`]); }); test('Double value like contains', async () => { @@ -2359,7 +2391,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2396,7 +2429,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([`contains( ${first} , ${second} )`]); + expect(mapValues).toEqual([`contains(${first}, ${second})`]); }); test('Sensor action in formula', async () => { @@ -2448,7 +2481,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2485,7 +2519,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([` touches finger + true `]); + expect(mapValues).toEqual([`touches finger + true`]); }); test('UserList in formula', async () => { @@ -2529,7 +2563,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2610,7 +2645,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2701,7 +2737,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2803,7 +2840,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -2904,7 +2942,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; const mapKeys = []; @@ -2940,7 +2979,7 @@ describe('Catroid to Catblocks parser tests', () => { ); expect(mapKeys).toEqual([categoryName]); - expect(mapValues).toEqual([`join( ${first} , ${second} , ${third} )`]); + expect(mapValues).toEqual([`join(${first}, ${second}, ${third})`]); }); test('multiple formula', async () => { @@ -2976,18 +3015,18 @@ describe('Catroid to Catblocks parser tests', () => { const second = ['2', ' world', 'flatten', 'INDEX_CURRENT_TOUCH']; const third = ['3', '!', 'INDEX_OF_ITEM']; const output = [ - `if then else( ${first[0].toLowerCase()} , ${second[0]} , ${third[0]} )`, + `if then else(${first[0].toLowerCase()}, ${second[0]}, ${third[0]})`, `length('${first[1]}')`, - `join('${first[2]}','${second[1]}','${third[1]}')`, + `join('${first[2]}', '${second[1]}', '${third[1]}')`, `flatten(*${first[3]}*)`, - `item's index( ${first[4]} , *${second[2]}*)`, - ` ${first[5].toLowerCase().replaceAll('_', ' ')} `, - ` number of looks `, - ` ${first[7].toLowerCase().replaceAll('_', ' ')} `, - ` ${first[8].toLowerCase().replaceAll('_', ' ')} `, - ` number of current touches `, - `index of current touch( ${first[10]} )`, - ` ${first[11].toLowerCase()} ` + `item's index(${first[4]}, *${second[2]}*)`, + `${first[5].toLowerCase().replaceAll('_', ' ')}`, + `number of looks`, + `${first[7].toLowerCase().replaceAll('_', ' ')}`, + `${first[8].toLowerCase().replaceAll('_', ' ')}`, + `number of current touches`, + `index of current touch(${first[10]})`, + `${first[11].toLowerCase()}` ]; const xmlString = ` @@ -3211,7 +3250,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = []; const mapKeys = []; const mapValues = []; @@ -3289,7 +3329,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -3335,10 +3376,7 @@ describe('Catroid to Catblocks parser tests', () => { const first = ['200', '#ff0000']; const second = ['600', '#fe0000']; const third = '1'; - const expectedOutput = [ - 'color at x y( 200 , 600 )', - "color equals color with % tolerance('#ff0000', '#fe0000')" - ]; + const expectedOutput = ['color at x y(200, 600)', "color equals color with % tolerance('#ff0000', '#fe0000')"]; const xmlString = ` @@ -3409,7 +3447,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; @@ -3452,12 +3491,12 @@ describe('Catroid to Catblocks parser tests', () => { test('Values in endBrick', async () => { const blockName = 'ParameterizedBrick'; const blockNameEnd = 'ParameterizedEndBrick'; - const categoryName = ['CATBLOCKS_ASSERT_LISTS_SELECTED', 'ASSERT_LOOP_ACTUAL', 'LIST_SELECTED']; + const categoryName = ['ASSERT_LOOP_ACTUAL', 'CATBLOCKS_ASSERT_LISTS_SELECTED', 'LIST_SELECTED']; const first = 'List (1)'; const second = 'List (2)'; const third = '0'; const fourth = 'List (3)'; - const expectedOutput = ['2 lists selected', ' 0 ', 'List (3)']; + const expectedOutput = [third, '2 lists selected', fourth]; const xmlString = ` @@ -3496,7 +3535,7 @@ describe('Catroid to Catblocks parser tests', () => { 5872c621-bb05-4bfb-9150-77e7021acc88 false - + NUMBER ${third} @@ -3520,7 +3559,8 @@ describe('Catroid to Catblocks parser tests', () => { `; const [programJSON, mapKeys, mapValues] = await page.evaluate(pXML => { - const programJSON = Test.Parser.convertProgramToJSONDebug(pXML); + Test.Parser.parseXmlString(pXML); + const programJSON = Test.Parser.xmlToCatblocksProject(); const formulaMap = programJSON.scenes[0].objectList[0].scriptList[0].brickList[0].formValues; diff --git a/webpack.release.build.config.js b/webpack.release.build.config.js index d13c197d..eeb2ea31 100644 --- a/webpack.release.build.config.js +++ b/webpack.release.build.config.js @@ -17,7 +17,7 @@ const releaseFolder = 'release' + (integrationTarget === 'share' ? '' : '_catroi module.exports = async function () { let versionInformation = '*** no version found ***'; - try { + /*try { await git.pull('--tags', '--ff-only'); const loadedTags = await git.tags({ '--points-at': 'HEAD' }); console.log('Loaded Tags:', loadedTags); @@ -26,7 +26,7 @@ module.exports = async function () { } } catch (error) { console.error('Error loading git tags.', error); - } + }*/ const configuration = { mode: 'production', diff --git a/yarn.lock b/yarn.lock index 07ce4b67..3edf7fae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1982,6 +1982,14 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-globals@^7.0.0: version "7.0.1" resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz" @@ -2000,11 +2008,21 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + acorn-walk@^8.0.2: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + acorn@^8.1.0, acorn@^8.8.1: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" @@ -2352,6 +2370,11 @@ braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.4: version "4.21.4" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz" @@ -2825,7 +2848,7 @@ debug@4.3.4, debug@^4.3.4: dependencies: ms "2.1.2" -decimal.js@^10.4.2: +decimal.js@^10.3.1, decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -5137,6 +5160,11 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" +nwsapi@^2.2.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.4.tgz#fd59d5e904e8e1f03c25a7d5a15cfa16c714a1e5" + integrity sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g== + nwsapi@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" @@ -6326,7 +6354,7 @@ toidentifier@1.0.1: resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tough-cookie@^4.1.2: +tough-cookie@^4.0.0, tough-cookie@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== @@ -6525,6 +6553,20 @@ vary@~1.1.2: resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" + w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" @@ -6776,7 +6818,7 @@ ws@8.10.0: resolved "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz" integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== -ws@^8.11.0: +ws@^8.11.0, ws@^8.8.0: version "8.13.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==