Parsing xml using Node

yes - sorry I thought about it afterwards adn don't think that is the issue/

no worries

the errors are tilting me much more :crazy_face: :joy:

i think i have it.... :crazy_face: :crazy_face: :crazy_face:

i used xml2js and had to install another Node.js package const glob = nativeRequire('glob');
use npm i glob to install it.

here's the code:

const path = nativeRequire('path')
const fs = nativeRequire('fs');
const xml2js = nativeRequire('xml2js');
const glob = nativeRequire('glob');
const files = glob.sync(path.join(__dirname, '../../Steinberg/Cubase/Expression Maps/Native Instruments/Symphony Series/*.expressionmap'));
const articulations = {}
const maps = {}

    files.forEach(function(f) {
        fs.readFile(f, function(err, data) {
            var parser = new xml2js.Parser({explicitArray: false, mergeAttrs: true});
            parser.parseString(data,  function(err, maps) {
                // parsing done here
                
                for (var inst in maps) {

                    try {
                   articulations[inst] = maps.InstrumentMap.member[1].list.obj[0].member[1].string.value
                   maps = maps.InstrumentMap.string.value
                    } catch (err) {
                        console.error(`failed to extract articulations from ${inst}.json:`)
                        console.error(err)
                    }

                }
                console.log('Extracted articulations:\n')
                console.log(articulations[inst])
                console.log(maps)
        }); 
    });
});

Which directory did you install your modules into?

in the custom module folder

1 Like

Haveing some more thinking time...

At the moment we're ripping all the xml out of all the maps in one go and this will all sit in the memory until we need to use it.

And also, the code currently only gets the first articulation in the map.
This gives me two problems - 1. I have nearly 300 maps
Some of those maps have 60 articulations. This means there could be up to 18000 articulations sitting in the memory...

So here's an approach - and it will reply on the sysex track names working and I will reference my tracks with the articulation map as follows:
image

We can then pull the map number from the sysex and only go and get that map.

//now accessing an xml in a file structure
const path = nativeRequire('path')
const fs = nativeRequire('fs');
const xml2js = nativeRequire('xml2js');
const glob = nativeRequire('glob');
//const files = glob.sync(path.join(__dirname, '../../Steinberg/Cubase/Expression Maps/Native Instruments/Symphony Series/*.expressionmap'));
const mapFiles = glob.sync(path.join(__dirname, '../ExpressionMaps/*.expressionmap'));
const articulations = {}
var maps = {}

var trackID = '117-7' //test this will come from the TRACK ID sysex stuff

mapFiles.forEach(function (mapFile) {

    //debug

    //console.log('Selected Tracks  ' + mapFile);  // List out all the expression maps
    //console.log('mapFiles length' + mapFiles.length) // gets the length of list of the files - number of expression maps in directory
    //debug

    var parser = new xml2js.Parser();

    if (mapFile.includes(trackID)) {

        console.log('Selected Map  ' + mapFile); 

        fs.readFile(mapFile, function (err, data) {
            parser.parseString(data, function (err, result) {

                //Need to add a loop here that collects all the articulations in the map, not just the one at 
                //InstrumentMap.member[1].list.obj[0].member[1].string.value
                // I need to collect the articulation and the corresponding color designation (1 - 6)
                //Colour is at: /InstrumentMap/member/list/obj/int[@name=""color""]
 
                //result id of the format
                //{
                //    InstrumentMap: { string: [[Object]], member: [[Object], [Object], [Object]] }
                //}

                console.dir(result);
                console.log('Done');
            });
        });
    }
});

Ok, so we've worked hard to solve a problem where we can now parse a lot of xml into the CM and create an object within the functions that contains all the data from the xml that we want. Credit must go to @gcat who has done most of the hard work.

I've now transferred this into a function that we should be able to call on eventually to populate the OSC labels etc.

By my JS skills are very limited and now what I can't do is get the function to return the object as a result so that I can use it in my wider code.

I've logged to console each exit point of each nested function and worked out where it gets stuck but can't work out how to get the data any further...

Could someone (@jean-emmanuel I know you're really busy but....) have a look and see why it is stuck at the point where I've commented. Many thanks if you choose to help a little!

//now accessing an xml in a file structure
const path = nativeRequire('path')
const fs = nativeRequire('fs');
const xml2js = nativeRequire('xml2js');
const glob = nativeRequire('glob');
//const files = glob.sync(path.join(__dirname, '../../Steinberg/Cubase/Expression Maps/Native Instruments/Symphony Series/*.expressionmap'));
const mapFiles = glob.sync(path.join(__dirname, '../ExpressionMaps/*.expressionmap'));
var artArr = []
var artColor = []
var trackID = '117-4' //test value -  this will come from the TRACK ID sysex stuff


buildMap()   //I need Build Map to populate Objects artArr and artColor and return them so I can use them in other places in the code


function buildMap() {

    mapFiles.forEach(function (mapFile) { //sends mapFile into the parser

        //debug

        //console.log('Selected Tracks  ' + mapFile);  // List out all the expression maps
        //console.log('mapFiles length' + mapFiles.length) // gets the length of list of the files - number of expression maps in directory
        //debug

        var parser = new xml2js.Parser({ explicitArray: false, mergeAttrs: true }); // 'mergeAttrs: true' was essential 

        if (mapFile.includes(trackID)) {


            console.log('Selected Map:  ' + mapFile);


            fs.readFile(mapFile, function (err, data) {

                parser.parseString(data, function (err, result) {

                    let art = result.InstrumentMap.member[1].list.obj;
                    for (let i = 0, len = art.length; i < len; i++) {

                        console.log('Articulation at poition: ' + i + '  ' + art[i].member[1].string.value + '; ' + 'Color Value: ' + art[i].int.value) // articulatins and color value 

                        artArr[i] = art[i].member[1].string.value
                        artColor[i] = art[i].int.value

                        
                    }
                    
                });

                //THE BELOW LOGS TO CONSOLE OK
                console.log('CHECK LOG artArr[0] ' + artArr[0])
                console.log('CHECK LOG artArr[1] ' + artArr[1])
                console.log('CHECK LOG artArr[2] ' + artArr[2])
                console.log('CHECK LOG artArr[3] ' + artArr[3])
                console.log('CHECK LOG artArr[4] ' + artArr[4])
                console.log('CHECK LOG artArr[5] ' + artArr[5])
                console.log('CHECK LOG artArr[6] ' + artArr[6])
                console.log('CHECK LOG artArr[7] ' + artArr[7]) 
            });
            //THE BELOW LOGS TO CONSOLE as UNDEFINED artArr and artColor get stuck here
            console.log('CHECK LOG 2 artArr[0] ' + artArr[0])
            console.log('CHECK LOG 2 artArr[1] ' + artArr[1])
            console.log('CHECK LOG 2 artArr[2] ' + artArr[2])
            console.log('CHECK LOG 2 artArr[3] ' + artArr[3])
            console.log('CHECK LOG 2 artArr[4] ' + artArr[4])
            console.log('CHECK LOG 2 artArr[5] ' + artArr[5])
            console.log('CHECK LOG 2 artArr[6] ' + artArr[6])
            console.log('CHECK LOG 2 artArr[7] ' + artArr[7]) 
        }

    });

}

console.log('Test log a value from artArr ' + artArr[3])  //Needs to return a value but currently returns 'undefined'
console.log('Test log a value from artColor ' + artColor[3])  //Needs to return a value but currently returns 'undefined'

I’ve just discovered asynchronous programming. Functions like fs.readFile() and parser.parseString() are asynchronous apparently…

Houston we may have a problem…

we have to declare the variables without 'var' inside the function. with this it worked:

//now accessing an xml in a file structure
const path = nativeRequire('path')
const fs = nativeRequire('fs');
const xml2js = nativeRequire('xml2js');
const glob = nativeRequire('glob');
const mapFiles = glob.sync(path.join(__dirname, '../../Steinberg/Expression Maps/*.expressionmap'));
//const mapFiles = glob.sync(path.join(__dirname, '../ExpressionMaps/*.expressionmap'));



buildMap()   //I need Build Map to populate Objects artArr and artColor and return them so I can use them in other places in the code


function buildMap() {

        artArr = []
        artColor = []
        trackID = '117-7' //test value -  this will come from the TRACK ID sysex stuff


    mapFiles.forEach(function (mapFile) { //sends mapFile into the parser

        //debug

        //console.log('Selected Tracks  ' + mapFile);  // List out all the expression maps
        //console.log('mapFiles length' + mapFiles.length) // gets the length of list of the files - number of expression maps in directory
        //debug

        var parser = new xml2js.Parser({ explicitArray: false, mergeAttrs: true }); // 'mergeAttrs: true' was essential 

        if (mapFile.includes(trackID)) {


            console.log('Selected Map:  ' + mapFile);


            fs.readFile(mapFile, function (err, data) {

                parser.parseString(data, function (err, result) {

                    let art = result.InstrumentMap.member[1].list.obj;
                    for (let i = 0, len = art.length; i < len; i++) {

                        console.log('Articulation at poition: ' + i + '  ' + art[i].member[1].string.value + '; ' + 'Color Value: ' + art[i].int.value) // articulatins and color value 

                        artArr[i] = art[i].member[1].string.value
                        artColor[i] = art[i].int.value

                        
                    }
                    
                });

                //THE BELOW LOGS TO CONSOLE OK
                console.log('CHECK LOG artArr[0] ' + artArr[0])
                console.log('CHECK LOG artArr[1] ' + artArr[1])
                console.log('CHECK LOG artArr[2] ' + artArr[2])
                console.log('CHECK LOG artArr[3] ' + artArr[3])
                console.log('CHECK LOG artArr[4] ' + artArr[4])
                console.log('CHECK LOG artArr[5] ' + artArr[5])
                console.log('CHECK LOG artArr[6] ' + artArr[6])
                console.log('CHECK LOG artArr[7] ' + artArr[7]) 
            });
            //THE BELOW LOGS TO CONSOLE as UNDEFINED artArr and artColor get stuck here
            console.log('CHECK LOG 2 artArr[0] ' + artArr[0])
            console.log('CHECK LOG 2 artArr[1] ' + artArr[1])
            console.log('CHECK LOG 2 artArr[2] ' + artArr[2])
            console.log('CHECK LOG 2 artArr[3] ' + artArr[3])
            console.log('CHECK LOG 2 artArr[4] ' + artArr[4])
            console.log('CHECK LOG 2 artArr[5] ' + artArr[5])
            console.log('CHECK LOG 2 artArr[6] ' + artArr[6])
            console.log('CHECK LOG 2 artArr[7] ' + artArr[7]) 
        }

    });

}

console.log('Test log a value from artArr ' + artArr[3])  //Needs to return a value but currently returns 'undefined'
console.log('Test log a value from artColor ' + artColor[3])  //Needs to return a value but currently returns 'undefined'

what does that mean?

Have a read of this….

I asked for a bit of help on stack

@gcat : cool...
code

too much reading...my head's still squashed :joy:

EDIT: i get the log2 now. i'll have a read later
i meant i got it. looked at the wrong log. :man_facepalming:

@Sylvain credit goes to @Sub3OneDay for this

I was about to reply about the asynchronous thing but it seems you found it !

Ok, so finally got back to my machine and have been through the Stack Overflow answer. I understand what they are saying about the asynchronous operations and see why that will be a problem.

Here's the new code:

//now accessing an xml in a file structure
const path = nativeRequire('path');
const fs = nativeRequire('fs');
const xml2js = nativeRequire('xml2js');
const glob = nativeRequire('glob');
const mapFiles = glob.sync(path.join(__dirname, '../ExpressionMaps/*.expressionmap'));
const trackID = '117-4' //test value -  this will come from the TRACK ID sysex stuff

async function buildMap() {
    const artArr = [];
    const artColor = [];
    for (const mapFile of mapFiles) {
        console.log('Selected Map:  ' + mapFile);
        if (mapFile.includes(trackID)) {
            const parser = new xml2js.Parser({
                explicitArray: false,
                mergeAttrs: true
            }); // 'mergeAttrs: true' was essential
            console.log('Selected Map:  ' + mapFile);
            const data = await fs.promises.readFile(mapFile);
            const result = await parser.parseString(data);
            let art = result.InstrumentMap.member[1].list.obj;
           // const art = result.InstrumentMap.member[1].list.obj;
            // NOTE: this code assumes that this part only ever runs for
            // one mapFile, that all the others don't match the trackID test
            // because if you run this more than once, it will just
            // overwrite the members of artArr and artColor
            for (let i = 0, len = art.length; i < len; i++) {
                console.log('Articulation at poition: ' + i + '  ' + art[i].member[1].string.value + '; ' +
                    'Color Value: ' + art[i].int.value) // articulatins and color value
                artArr[i] = art[i].member[1].string.value
                artColor[i] = art[i].int.value
            }
            break;
        }
    }
    return { artArr, artColor };
}


buildMap().then(result => {
    // use your results here
    console.log(result);
}).catch(err => {
    console.log(err);
});

However, we now seem to be coming up with a problem. The code runs ok in so far as it gets the correct xml map but it fails at let art = result.InstrumentMap.member[1].list.obj; and I get an error message saying TypeError: Cannot read properties of undefined (reading 'member') at buildMap

image

This seems to say that the code can't now access the xml file, which it could do before? Are we back to the beginning??!

that's about as far as i got. i understand the logic.

checked a few youtube videos about sync and async. but my brain is not at deep thinking level yet. :mask:
maybe i'll try later this evening.

EDIT:

i think we have the same problem as befor. logging outside the function gets an error:

EDIT2:

when we remove the const of 'artArr' and 'artColor' then we can reference them outside the function.

Someone answered on stack :

I corrected one line of code. Change const result = await parser.parseString(data); to const result = await parser.parseStringPromise(data); . The xml2js did not support promises the same way almost every other library does - no idea why. Anyway, it is corrected now.

It works now :

Now we’re cooking…. This is going to be amazing….

I just need ti implement the sysex thing first…

AMAZING!!!
can you reference artArr and artColor outside now. i thought i have it before but now for me this part is not working:

or am i missing something?

Bildschirmfoto 2022-01-23 um 20.36.13

how do we access our articulations?

i can't console.log outside the function but somehow my buttons work.

HALLE “#$%& LUJAH

1 Like