<commit_before>"use strict";

// TODO: test and improve error handling (e.g invalid XML)

var fs = require( 'fs' ),
    Q = require( 'q' ),
    crypto = require( 'crypto' ),
    path = require( 'path' ),
    url = require( 'url' ),
    utils = require( '../lib/utils' ),
    libxmljs = require( "libxmljs" ),
    debug = require( 'debug' )( 'form model' ),
    FORMSTORAGEPATH = path.resolve( __dirname, '../../storage/forms' );

module.exports = {
    getXFormList: getXFormList,
    getManifest: getManifest
};

function getXFormList( baseUrl, verbose ) {
    var xform, id,
        formList = new libxmljs.Document(),
        deferred = Q.defer(),
        xforms = formList.node( 'xforms' ),
        tasks = [];

    // set namespace
    xforms.namespace( "http://openrosa.org/xforms/xformsList" );

    // find the forms in storage
    fs.readdir( FORMSTORAGEPATH, function( error, files ) {
        if ( error ) {
            deferred.reject( error );
        } else {
            files.forEach( function( file ) {
                if ( file.indexOf( '.xml' ) >= 0 ) {
                    id = file.substring( 0, file.length - 4 );
                    tasks.push( new Xform( id ).getProperties( baseUrl, verbose ) );
                }
            } );
            Q.all( tasks )
                .then( function() {
                    arguments[ 0 ].forEach( function( formObj ) {
                        xform = xforms.node( 'xform' );
                        for ( var property in formObj ) {
                            xform.node( property, formObj[ property ] );
                        }
                    } );
                    deferred.resolve( formList );
                } )
                .catch( function( error ) {
                    debug( 'error in getXFOrmList', error );
                    deferred.reject( error );
                } );
        }
    } );

    return deferred.promise;
}

function getManifest( id, baseUrl ) {
    var mediaFile,
        doc = new libxmljs.Document(),
        manifest = doc.node( 'manifest' ),
        deferred = Q.defer(),
        tasks = [];

    manifest.namespace( "http://openrosa.org/xforms/xformsManifest" );

    fs.readdir( path.join( FORMSTORAGEPATH, id + '-media' ), function( error, files ) {
        if ( error || files.length === 0 ) {
            deferred.reject( error );
        } else {
            files.forEach( function( file ) {
                tasks.push( new MediaFile( file, id ).getProperties( baseUrl ) );
            } );

            Q.all( tasks )
                .then( function() {
                    arguments[ 0 ].forEach( function( mediaObj ) {
                        mediaFile = manifest.node( 'mediaFile' );
                        for ( var property in mediaObj ) {
                            mediaFile.node( property, mediaObj[ property ] );
                        }
                    } );
                    deferred.resolve( manifest );
                } )
                .catch( function( error ) {
                    debug( 'error in manifest creation', error );
                    deferred.reject( error );
                } );

        }
    } );

    return deferred.promise;
}

/**
 * XForm Class
 * @param {[type]} id [description]
 */
function Xform( id ) {
    var that = this,
        deferred = Q.defer(),
        xformPath = path.join( FORMSTORAGEPATH, id + '.xml' );

    this.id = id;

    // mimicking future async db query
    fs.readFile( xformPath, 'utf-8', function( error, data ) {
        if ( error ) {
            deferred.reject( error );
        } else {
            // remove the pesky default namespace
            that.data = data.replace( / xmlns="http:\/\/www\.w3\.org\/2002\/xforms"/, '' );
            // that.data = '<data>dfdsa';
            try {
                that.doc = libxmljs.parseXml( that.data );
                deferred.resolve( true );
            } catch ( e ) {
                var err = new Error( 'XML Error: ' + JSON.stringify( e ) );
                deferred.reject( err );
            }
        }
    } );

    this.initialize = function() {
        return deferred.promise;
    };
}

Xform.prototype.getProperties = function( baseUrl, verbose ) {
    var props, manifestUrl,
        xform = this;

    return this.initialize()
        .then( function() {
            props = {
                formID: xform._getFormId(),
                name: xform._getName(),
                majorMinorVersion: xform._getMajorMinorVersion(),
                version: xform._getVersion(),
                hash: xform._getHash(),
                downloadUrl: xform._getDownloadUrl( baseUrl )
            };
            if ( verbose ) {
                props.descriptionText = xform._getDescriptionText();
                props.descriptionUrl = xform._getDescriptionUrl();
            }
            return xform._getManifestUrl( baseUrl )
                .then( function( manifestUrl ) {
                    if ( manifestUrl ) {
                        props.manifestUrl = manifestUrl;
                    }
                    return props;
                } );
        } );
};

Xform.prototype._getFormId = function() {
    var id;

    id = this.doc.get( '//model/instance/node()[@id]' );
    if ( !id ) {
        throw new Error( 'id attribute not found' );
    }
    // there has to be a better way to get this id and version...
    id = id.attr( 'id' ).toString();
    return id.substring( 5, id.length - 1 );
};

Xform.prototype._getName = function() {
    var title = this.doc.get( '//h:head/h:title', {
        h: "http://www.w3.org/1999/xhtml"
    } );

    if ( !title ) {
        throw new Error( 'title element not found' );
    }
    return title.text();
};

Xform.prototype._getMajorMinorVersion = function() {
    return '';
};

Xform.prototype._getVersion = function() {
    var version;

    version = this.doc.get( '//model/instance/node()[@version]' );
    if ( !version ) {
        return '';
    }
    // there has to be a better way to get this version...
    version = version.attr( 'version' ).toString();
    return version.substring( 10, version.length - 1 );
};

Xform.prototype._getHash = function() {
    return 'md5:' + utils.md5( this.data );
};

Xform.prototype._getDescriptionText = function() {
    return this._getName();
};

Xform.prototype._getDescriptionUrl = function() {
    return '';
};

Xform.prototype._getDownloadUrl = function( baseUrl ) {
    return url.resolve( baseUrl, path.join( 'form', this.id, 'form.xml' ) );
};<commit_msg>improved use of namespaces to query document<commit_after>"use strict";

// TODO: test and improve error handling (e.g invalid XML)

var fs = require( 'fs' ),
    Q = require( 'q' ),
    crypto = require( 'crypto' ),
    path = require( 'path' ),
    url = require( 'url' ),
    utils = require( '../lib/utils' ),
    libxmljs = require( "libxmljs" ),
    debug = require( 'debug' )( 'form model' ),
    FORMSTORAGEPATH = path.resolve( __dirname, '../../storage/forms' );

module.exports = {
    getXFormList: getXFormList,
    getManifest: getManifest
};

function getXFormList( baseUrl, verbose ) {
    var xform, id,
        formList = new libxmljs.Document(),
        deferred = Q.defer(),
        xforms = formList.node( 'xforms' ),
        tasks = [];

    // set namespace
    xforms.namespace( "http://openrosa.org/xforms/xformsList" );

    // find the forms in storage
    fs.readdir( FORMSTORAGEPATH, function( error, files ) {
        if ( error ) {
            deferred.reject( error );
        } else {
            files.forEach( function( file ) {
                if ( file.indexOf( '.xml' ) >= 0 ) {
                    id = file.substring( 0, file.length - 4 );
                    tasks.push( new Xform( id ).getProperties( baseUrl, verbose ) );
                }
            } );
            Q.all( tasks )
                .then( function() {
                    arguments[ 0 ].forEach( function( formObj ) {
                        xform = xforms.node( 'xform' );
                        for ( var property in formObj ) {
                            xform.node( property, formObj[ property ] );
                        }
                    } );
                    deferred.resolve( formList );
                } )
                .catch( function( error ) {
                    debug( 'error in getXFOrmList', error );
                    deferred.reject( error );
                } );
        }
    } );

    return deferred.promise;
}

function getManifest( id, baseUrl ) {
    var mediaFile,
        doc = new libxmljs.Document(),
        manifest = doc.node( 'manifest' ),
        deferred = Q.defer(),
        tasks = [];

    manifest.namespace( "http://openrosa.org/xforms/xformsManifest" );

    fs.readdir( path.join( FORMSTORAGEPATH, id + '-media' ), function( error, files ) {
        if ( error || files.length === 0 ) {
            deferred.reject( error );
        } else {
            files.forEach( function( file ) {
                tasks.push( new MediaFile( file, id ).getProperties( baseUrl ) );
            } );

            Q.all( tasks )
                .then( function() {
                    arguments[ 0 ].forEach( function( mediaObj ) {
                        mediaFile = manifest.node( 'mediaFile' );
                        for ( var property in mediaObj ) {
                            mediaFile.node( property, mediaObj[ property ] );
                        }
                    } );
                    deferred.resolve( manifest );
                } )
                .catch( function( error ) {
                    debug( 'error in manifest creation', error );
                    deferred.reject( error );
                } );

        }
    } );

    return deferred.promise;
}

/**
 * XForm Class
 * @param {[type]} id [description]
 */
function Xform( id ) {
    var that = this,
        deferred = Q.defer(),
        xformPath = path.join( FORMSTORAGEPATH, id + '.xml' );

    this.id = id;

    // mimicking future async db query
    fs.readFile( xformPath, 'utf-8', function( error, data ) {
        if ( error ) {
            deferred.reject( error );
        } else {
            that.data = data;
            //that.data = '<data>dfdsa';
            try {
                that.doc = libxmljs.parseXml( that.data );
                that.namespaces = that._getNamespaces();

                //debug( 'defaultNamespace', JSON.stringify( that.defaultNamespace[ 0 ] ) );
                deferred.resolve( true );
            } catch ( e ) {
                var err = new Error( 'XML Error in form "' + that.id + '": ' + JSON.stringify( e ) );
                deferred.reject( err );
            }
        }
    } );

    this.initialize = function() {
        return deferred.promise;
    };
}

Xform.prototype.getProperties = function( baseUrl, verbose ) {
    var props, manifestUrl,
        xform = this;

    return this.initialize()
        .then( function() {
            props = {
                formID: xform._getFormId(),
                name: xform._getName(),
                majorMinorVersion: xform._getMajorMinorVersion(),
                version: xform._getVersion(),
                hash: xform._getHash(),
                downloadUrl: xform._getDownloadUrl( baseUrl )
            };
            if ( verbose ) {
                props.descriptionText = xform._getDescriptionText();
                props.descriptionUrl = xform._getDescriptionUrl();
            }
            return xform._getManifestUrl( baseUrl )
                .then( function( manifestUrl ) {
                    if ( manifestUrl ) {
                        props.manifestUrl = manifestUrl;
                    }
                    return props;
                } );
        } );
};

Xform.prototype._getNamespaces = function() {
    // TODO: extract these from this.doc instead
    return {
        xmlns: "http://www.w3.org/2002/xforms",
        h: "http://www.w3.org/1999/xhtml",
        jr: "http://openrosa.org/javarosa",
        orx: "http://openrosa.org/xforms/",
        xsd: "http://www.w3.org/2001/XMLSchema",
        ev: "http://www.w3.org/2001/xml-events"
    };
};

Xform.prototype._getFormId = function() {
    var id;

    id = this.doc.get( '//xmlns:model/xmlns:instance/node()[@id]', this.namespaces );
    if ( !id ) {
        throw new Error( 'id attribute not found for form "' + this.id + '"' );
    }
    // there has to be a better way to get this id and version...
    id = id.attr( 'id' ).toString();
    return id.substring( 5, id.length - 1 );
};

Xform.prototype._getName = function() {
    var title = this.doc.get( '//h:head/h:title', this.namespaces );

    if ( !title ) {
        throw new Error( 'title element not found for form "' + this.id + '"' );
    }
    return title.text();
};

Xform.prototype._getMajorMinorVersion = function() {
    return '';
};

Xform.prototype._getVersion = function() {
    var version;

    version = this.doc.get( '//xmlns:model/xmlns:instance/node()[@version]', this.namespaces );
    if ( !version ) {
        return '';
    }
    // there has to be a better way to get this version...
    version = version.attr( 'version' ).toString();
    return version.substring( 10, version.length - 1 );
};

Xform.prototype._getHash = function() {
    return 'md5:' + utils.md5( this.data );
};

Xform.prototype._getDescriptionText = function() {
    return this._getName();
};

Xform.prototype._getDescriptionUrl = function() {
    return '';
};

Xform.prototype._getDownloadUrl = function( baseUrl ) {
    return url.resolve( baseUrl, path.join( 'form', this.id, 'form.xml' ) );
};
{'fraction_non_alphanumeric': 0.13578978273634762, 'fraction_numerical': 0.004330593071051087, 'mean_word_length': 1.5952380952380953, 'pattern_counts': {'":': 1, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 23}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7104948', 'n_tokens_mistral': 4003, 'n_tokens_neox': 3815, 'n_words': 920}