<commit_before>/*global JSON*/
var Stream = require('stream'),
    _ = require('underscore'),
    gm = require('gm'),
    mime = require('mime'),
    exifReader = require('exif-reader'),
    icc = require('icc'),
    sharp,
    isOperationByEngineNameAndName = {gm: {}},
    filterConstructorByOperationName = {},
    errors = require('./errors');

['PngQuant', 'PngCrush', 'OptiPng', 'JpegTran', 'Inkscape', 'SvgFilter'].forEach(function (constructorName) {
    try {
        filterConstructorByOperationName[constructorName.toLowerCase()] = require(constructorName.toLowerCase());
    } catch (e) {
        // SvgFilter might fail because of failed contextify installation on windows.
        // Dependency chain to contextify: svgfilter --> assetgraph --> jsdom --> contextify
    }
});

Object.keys(gm.prototype).forEach(function (propertyName) {
    if (!/^_|^(?:emit|.*Listeners?|on|once|size|orientation|format|depth|color|res|filesize|identity|write|stream)$/.test(propertyName) &&
        typeof gm.prototype[propertyName] === 'function') {
        isOperationByEngineNameAndName.gm[propertyName] = true;
    }
});

function getMockFileNameForContentType(contentType) {
    if (contentType) {
        if (contentType === 'image/vnd.microsoft.icon') {
            return '.ico';
        }
        return mime.extensions[contentType];
    }
}

// For compatibility with the sharp format switchers (minus webp, which graphicsmagick doesn't support).
// Consider adding more from this list: gm convert -list format
['jpeg', 'png'].forEach(function (formatName) {
    isOperationByEngineNameAndName.gm[formatName] = true;
});

try {
    sharp = require('sharp');
} catch (e) {}

if (sharp) {
    isOperationByEngineNameAndName.sharp = {};
    ['resize', 'extract', 'sequentialRead', 'crop', 'max', 'background', 'embed', 'flatten', 'rotate', 'flip', 'flop', 'withoutEnlargement', 'ignoreAspectRatio', 'sharpen', 'interpolateWith', 'gamma', 'grayscale', 'greyscale', 'jpeg', 'png', 'webp', 'quality', 'progressive', 'withMetadata', 'compressionLevel'].forEach(function (sharpOperationName) {
        isOperationByEngineNameAndName.sharp[sharpOperationName] = true;
    });
}

var engineNamesByOperationName = {};

Object.keys(isOperationByEngineNameAndName).forEach(function (engineName) {
    Object.keys(isOperationByEngineNameAndName[engineName]).forEach(function (operationName) {
        (engineNamesByOperationName[operationName] = engineNamesByOperationName[operationName] || []).push(engineName);
    });
});

module.exports = function getFilterInfosAndTargetContentTypeFromQueryString(queryString, options) {
    options = options || {};
    var filters = options.filters || {},
        filterInfos = [],
        defaultEngineName = options.defaultEngineName || (sharp && 'sharp') || 'gm',
        currentEngineName,
        operations = [],
        operationNames = [],
        usedQueryStringFragments = [],
        leftOverQueryStringFragments = [],
        sourceMetadata = options.sourceMetadata || {},
        targetContentType = options.sourceMetadata.contentType,
        root = options.root || options.rootPath;

    function checkSharpOrGmOperation(operation) {
        if (operation.name === 'resize' && typeof options.maxOutputPixels === 'number' && operation.args.length >= 2 && operation.args[0] * operation.args[1] > options.maxOutputPixels) {
            // FIXME: Realizing that we're going over the limit when only one resize operand is given would require knowing the metadata.
            // It's a big wtf that the maxOutputPixels option is only enforced some of the time.
            throw new errors.OutputDimensionsExceeded('resize: Target dimensions of ' + operation.args[0] + 'x' + operation.args[1] + ' exceed maxOutputPixels (' + options.maxOutputPixels + ')');
        }
    }

    function flushOperations() {
        if (operations.length > 0) {
            if (currentEngineName === 'sharp') {
                var sharpOperationsForThisInstance = [].concat(operations);
                operationNames.push('sharp');
                filterInfos.push({
                    operationName: 'sharp',
                    usedQueryStringFragments: operations.map(function (operation) {
                        return operation.usedQueryStringFragment;
                    }),
                    create: function () {
                        if (options.maxInputPixels) {
                            sharpOperationsForThisInstance.unshift({name: 'limitInputPixels', args: [options.maxInputPixels]});
                        }
                        return sharpOperationsForThisInstance.reduce(function (sharpInstance, operation) {
                            checkSharpOrGmOperation(operation);
                            var args = operation.args;
                            // Compensate for https://github.com/lovell/sharp/issues/276
                            if (operation.name === 'extract' && args.length > 2) {
                                args = [args[1], args[0]].concat(args.slice(2));
                            }
                            return sharpInstance[operation.name].apply(sharpInstance, args);
                        }, sharp());
                    }
                });
            } else if (currentEngineName === 'gm') {
                var gmOperationsForThisInstance = [].concat(operations);
                operationNames.push('gm');
                filterInfos.push({
                    operationName: 'gm',
                    usedQueryStringFragments: operations.map(function (operation) {
                        return operation.usedQueryStringFragment;
                    }),
                    create: function () {
                        // For some reason the gm module doesn't expose itself as a readable/writable stream,
                        // so we need to wrap it into one:

                        var readStream = new Stream();
                        readStream.readable = true;

                        var readWriteStream = new Stream();
                        readWriteStream.readable = readWriteStream.writable = true;
                        var spawned = false;

                        readWriteStream.write = function (chunk) {
                            if (!spawned) {
                                spawned = true;
                                var seenData = false,
                                    hasEnded = false,
                                    gmInstance = gm(readStream, getMockFileNameForContentType(gmOperationsForThisInstance[0].sourceContentType));
                                if (options.maxInputPixels) {
                                    gmInstance.limit('pixels', options.maxInputPixels);
                                }
                                gmOperationsForThisInstance.reduce(function (gmInstance, gmOperation) {
                                    checkSharpOrGmOperation(gmOperation);
                                    if (!gmInstance[gmOperation.name]) {
                                        gmOperation = _.extend({}, gmOperation);
                                        gmOperation.args.unshift(gmOperation.name);
                                        gmOperation.name = 'setFormat';
                                    }
                                    return gmInstance[gmOperation.name].apply(gmInstance, gmOperation.args);
                                }, gmInstance).stream(function (err, stdout, stderr) {
                                    if (err) {
                                        hasEnded = true;
                                        return readWriteStream.emit('error', err);
                                    }
                                    stdout.on('data', function (chunk) {
                                        seenData = true;
                                        readWriteStream.emit('data', chunk);
                                    }).on('end', function () {
                                        if (!hasEnded) {
                                            if (seenData) {
                                                readWriteStream.emit('end');
                                            } else {
                                                readWriteStream.emit('error', new Error('The gm stream ended without emitting any data'));
                                            }
                                            hasEnded = true;
                                        }
                                    });
                                });
                            }
                            readStream.emit('data', chunk);
                        };
                        readWriteStream.end = function (chunk) {
                            if (chunk) {
                                readWriteStream.write(chunk);
                            }
                            readStream.emit('end');
                        };
                        return readWriteStream;
                    }
                });
            } else {
                throw new Error('Internal error');
            }
            operations = [];
        }
        currentEngineName = undefined;
    }

    queryString.split('&').forEach(function (keyValuePair) {
        var matchKeyValuePair = keyValuePair.match(/^([^=]+)(?:=(.*))?/);
        if (matchKeyValuePair) {
            var operationName = decodeURIComponent(matchKeyValuePair[1]),
                // Split by non-URL encoded comma or plus:
                operationArgs = matchKeyValuePair[2] ? matchKeyValuePair[2].split(/[\+,]/).map(function (arg) {
                    arg = decodeURIComponent(arg);
                    if (/^\d+$/.test(arg)) {
                        return parseInt(arg, 10);
                    } else if (arg === 'true') {
                        return true;
                    } else if (arg === 'false') {
                        return false;
                    } else {
                        return arg;
                    }
                }) : [];

            var filterInfo;
            if (filters[operationName]) {
                flushOperations();
                filterInfo = filters[operationName](operationArgs, {
                    numPreceedingFilters: filterInfos.length
                });
                if (filterInfo) {
                    filterInfo.usedQueryStringFragments = [keyValuePair];
                    filterInfo.operationName = operationName;
                    if (filterInfo.outputContentType) {
                        targetContentType = filterInfo.outputContentType;
                    }
                    filterInfos.push(filterInfo);
                    operationNames.push(operationName);
                    usedQueryStringFragments.push(keyValuePair);
                } else {
                    leftOverQueryStringFragments.push(keyValuePair);
                }
            } else if (operationName === 'metadata' && sharp) {
                flushOperations();
                targetContentType = 'application/json; charset=utf-8';
                filterInfos.push({
                    metadata: true,
                    outputContentType: targetContentType,
                    create: function () {
                        var sharpInstance = sharp();
                        var duplexStream = new Stream.Duplex();
                        duplexStream._write = function (chunk, encoding, cb) {
                            if (sharpInstance.write(chunk, encoding) === false) {
                                sharpInstance.once('drain', cb);
                            } else {
                                cb();
                            }
                        };
                        duplexStream._read = function (size) {
                            sharpInstance.metadata().then(function (metadata) {
                                if (metadata.format) {
                                    // metadata.format is one of 'jpeg', 'png', 'webp' so this should be safe:
                                    metadata.contentType = 'image/' + metadata.format;
                                }
                                _.defaults(metadata, sourceMetadata);
                                if (metadata.exif) {
                                    var exifData;
                                    try {
                                        exifData = exifReader(metadata.exif);
                                    } catch (e) {
                                        // Error: Invalid EXIF
                                    }
                                    metadata.exif = undefined;
                                    if (exifData) {
                                        _.defaults(metadata, exifData);
                                    }
                                }
                                if (metadata.icc) {
                                    try {
                                        metadata.icc = icc.parse(metadata.icc);
                                    } catch (e) {
                                        // Error: Error: Invalid ICC profile, remove the Buffer
                                        metadata.icc = undefined;
                                    }
                                }
                                duplexStream.push(JSON.stringify(metadata));
                                duplexStream.push(null);
                            }, function (err) {
                                duplexStream.emit('error', err);
                            });
                        };
                        duplexStream.on('finish', function () {
                            sharpInstance.end();
                        });
                        return duplexStream;
                    }
                });
            } else if (isOperationByEngineNameAndName[operationName]) {
                usedQueryStringFragments.push(keyValuePair);
                flushOperations();
                defaultEngineName = operationName;
            } else if (engineNamesByOperationName[operationName]) {
                // Check if at least one of the engines supporting this operation is allowed
                var candidateEngineNames = engineNamesByOperationName[operationName].filter(function (engineName) {
                    return filters[engineName] !== false;
                });
                if (candidateEngineNames.length > 0) {
                    if (currentEngineName && !isOperationByEngineNameAndName[currentEngineName]) {
                        flushOperations();
                    }

                    if (!currentEngineName || candidateEngineNames.indexOf(currentEngineName) === -1) {
                        if (candidateEngineNames.indexOf(defaultEngineName) !== -1) {
                            currentEngineName = defaultEngineName;
                        } else {
                            currentEngineName = candidateEngineNames[0];
                        }
                    }
                    var sourceContentType = targetContentType;
                    if (operationName === 'setFormat' && operationArgs.length > 0) {
                        var targetFormat = operationArgs[0].toLowerCase();
                        if (targetFormat === 'jpg') {
                            targetFormat = 'jpeg';
                        }
                        targetContentType = 'image/' + targetFormat;
                    } else if (operationName === 'jpeg' || operationName === 'png' || operationName === 'webp') {
                        targetContentType = 'image/' + operationName;
                    }
                    operations.push({sourceContentType: sourceContentType, name: operationName, args: operationArgs, usedQueryStringFragment: keyValuePair});
                    usedQueryStringFragments.push(keyValuePair);
                }
            } else {
                var operationNameLowerCase = operationName.toLowerCase(),
                    FilterConstructor = filterConstructorByOperationName[operationNameLowerCase];
                if (FilterConstructor && filters[operationNameLowerCase] !== false) {
                    operationNames.push(operationNameLowerCase);
                    flushOperations();
                    if (operationNameLowerCase === 'svgfilter' && root && options.sourceFilePath) {
                        operationArgs.push('--root', 'file://' + root, '--url', 'file://' + options.sourceFilePath);
                    }
                    filterInfo = {
                        create: function () {
                            return new FilterConstructor(operationArgs);
                        },
                        operationName: operationNameLowerCase,
                        usedQueryStringFragments: [keyValuePair]
                    };
                    filterInfos.push(filterInfo);
                    usedQueryStringFragments.push(keyValuePair);
                    if (operationNameLowerCase === 'inkscape') {
                        var filter = filterInfo.create();
                        filterInfo.create = function () {
                            return filter;
                        };
                        targetContentType = 'image/' + filter.outputFormat;
                    }
                } else {
                    leftOverQueryStringFragments.push(keyValuePair);
                }
            }
        }
    });
    flushOperations();

    return {
        targetContentType: targetContentType,
        operationNames: operationNames,
        filterInfos: filterInfos,
        usedQueryStringFragments: usedQueryStringFragments,
        leftOverQueryStringFragments: leftOverQueryStringFragments
    };
};
<commit_msg>Actually, just use the new .extract(options) syntax for clarity.<commit_after>/*global JSON*/
var Stream = require('stream'),
    _ = require('underscore'),
    gm = require('gm'),
    mime = require('mime'),
    exifReader = require('exif-reader'),
    icc = require('icc'),
    sharp,
    isOperationByEngineNameAndName = {gm: {}},
    filterConstructorByOperationName = {},
    errors = require('./errors');

['PngQuant', 'PngCrush', 'OptiPng', 'JpegTran', 'Inkscape', 'SvgFilter'].forEach(function (constructorName) {
    try {
        filterConstructorByOperationName[constructorName.toLowerCase()] = require(constructorName.toLowerCase());
    } catch (e) {
        // SvgFilter might fail because of failed contextify installation on windows.
        // Dependency chain to contextify: svgfilter --> assetgraph --> jsdom --> contextify
    }
});

Object.keys(gm.prototype).forEach(function (propertyName) {
    if (!/^_|^(?:emit|.*Listeners?|on|once|size|orientation|format|depth|color|res|filesize|identity|write|stream)$/.test(propertyName) &&
        typeof gm.prototype[propertyName] === 'function') {
        isOperationByEngineNameAndName.gm[propertyName] = true;
    }
});

function getMockFileNameForContentType(contentType) {
    if (contentType) {
        if (contentType === 'image/vnd.microsoft.icon') {
            return '.ico';
        }
        return mime.extensions[contentType];
    }
}

// For compatibility with the sharp format switchers (minus webp, which graphicsmagick doesn't support).
// Consider adding more from this list: gm convert -list format
['jpeg', 'png'].forEach(function (formatName) {
    isOperationByEngineNameAndName.gm[formatName] = true;
});

try {
    sharp = require('sharp');
} catch (e) {}

if (sharp) {
    isOperationByEngineNameAndName.sharp = {};
    ['resize', 'extract', 'sequentialRead', 'crop', 'max', 'background', 'embed', 'flatten', 'rotate', 'flip', 'flop', 'withoutEnlargement', 'ignoreAspectRatio', 'sharpen', 'interpolateWith', 'gamma', 'grayscale', 'greyscale', 'jpeg', 'png', 'webp', 'quality', 'progressive', 'withMetadata', 'compressionLevel'].forEach(function (sharpOperationName) {
        isOperationByEngineNameAndName.sharp[sharpOperationName] = true;
    });
}

var engineNamesByOperationName = {};

Object.keys(isOperationByEngineNameAndName).forEach(function (engineName) {
    Object.keys(isOperationByEngineNameAndName[engineName]).forEach(function (operationName) {
        (engineNamesByOperationName[operationName] = engineNamesByOperationName[operationName] || []).push(engineName);
    });
});

module.exports = function getFilterInfosAndTargetContentTypeFromQueryString(queryString, options) {
    options = options || {};
    var filters = options.filters || {},
        filterInfos = [],
        defaultEngineName = options.defaultEngineName || (sharp && 'sharp') || 'gm',
        currentEngineName,
        operations = [],
        operationNames = [],
        usedQueryStringFragments = [],
        leftOverQueryStringFragments = [],
        sourceMetadata = options.sourceMetadata || {},
        targetContentType = options.sourceMetadata.contentType,
        root = options.root || options.rootPath;

    function checkSharpOrGmOperation(operation) {
        if (operation.name === 'resize' && typeof options.maxOutputPixels === 'number' && operation.args.length >= 2 && operation.args[0] * operation.args[1] > options.maxOutputPixels) {
            // FIXME: Realizing that we're going over the limit when only one resize operand is given would require knowing the metadata.
            // It's a big wtf that the maxOutputPixels option is only enforced some of the time.
            throw new errors.OutputDimensionsExceeded('resize: Target dimensions of ' + operation.args[0] + 'x' + operation.args[1] + ' exceed maxOutputPixels (' + options.maxOutputPixels + ')');
        }
    }

    function flushOperations() {
        if (operations.length > 0) {
            if (currentEngineName === 'sharp') {
                var sharpOperationsForThisInstance = [].concat(operations);
                operationNames.push('sharp');
                filterInfos.push({
                    operationName: 'sharp',
                    usedQueryStringFragments: operations.map(function (operation) {
                        return operation.usedQueryStringFragment;
                    }),
                    create: function () {
                        if (options.maxInputPixels) {
                            sharpOperationsForThisInstance.unshift({name: 'limitInputPixels', args: [options.maxInputPixels]});
                        }
                        return sharpOperationsForThisInstance.reduce(function (sharpInstance, operation) {
                            checkSharpOrGmOperation(operation);
                            var args = operation.args;
                            // Compensate for https://github.com/lovell/sharp/issues/276
                            if (operation.name === 'extract' && args.length >= 4) {
                                args = [ { left: args[0], top: args[1], width: args[2], height: args[3] } ];
                            }
                            return sharpInstance[operation.name].apply(sharpInstance, args);
                        }, sharp());
                    }
                });
            } else if (currentEngineName === 'gm') {
                var gmOperationsForThisInstance = [].concat(operations);
                operationNames.push('gm');
                filterInfos.push({
                    operationName: 'gm',
                    usedQueryStringFragments: operations.map(function (operation) {
                        return operation.usedQueryStringFragment;
                    }),
                    create: function () {
                        // For some reason the gm module doesn't expose itself as a readable/writable stream,
                        // so we need to wrap it into one:

                        var readStream = new Stream();
                        readStream.readable = true;

                        var readWriteStream = new Stream();
                        readWriteStream.readable = readWriteStream.writable = true;
                        var spawned = false;

                        readWriteStream.write = function (chunk) {
                            if (!spawned) {
                                spawned = true;
                                var seenData = false,
                                    hasEnded = false,
                                    gmInstance = gm(readStream, getMockFileNameForContentType(gmOperationsForThisInstance[0].sourceContentType));
                                if (options.maxInputPixels) {
                                    gmInstance.limit('pixels', options.maxInputPixels);
                                }
                                gmOperationsForThisInstance.reduce(function (gmInstance, gmOperation) {
                                    checkSharpOrGmOperation(gmOperation);
                                    if (!gmInstance[gmOperation.name]) {
                                        gmOperation = _.extend({}, gmOperation);
                                        gmOperation.args.unshift(gmOperation.name);
                                        gmOperation.name = 'setFormat';
                                    }
                                    return gmInstance[gmOperation.name].apply(gmInstance, gmOperation.args);
                                }, gmInstance).stream(function (err, stdout, stderr) {
                                    if (err) {
                                        hasEnded = true;
                                        return readWriteStream.emit('error', err);
                                    }
                                    stdout.on('data', function (chunk) {
                                        seenData = true;
                                        readWriteStream.emit('data', chunk);
                                    }).on('end', function () {
                                        if (!hasEnded) {
                                            if (seenData) {
                                                readWriteStream.emit('end');
                                            } else {
                                                readWriteStream.emit('error', new Error('The gm stream ended without emitting any data'));
                                            }
                                            hasEnded = true;
                                        }
                                    });
                                });
                            }
                            readStream.emit('data', chunk);
                        };
                        readWriteStream.end = function (chunk) {
                            if (chunk) {
                                readWriteStream.write(chunk);
                            }
                            readStream.emit('end');
                        };
                        return readWriteStream;
                    }
                });
            } else {
                throw new Error('Internal error');
            }
            operations = [];
        }
        currentEngineName = undefined;
    }

    queryString.split('&').forEach(function (keyValuePair) {
        var matchKeyValuePair = keyValuePair.match(/^([^=]+)(?:=(.*))?/);
        if (matchKeyValuePair) {
            var operationName = decodeURIComponent(matchKeyValuePair[1]),
                // Split by non-URL encoded comma or plus:
                operationArgs = matchKeyValuePair[2] ? matchKeyValuePair[2].split(/[\+,]/).map(function (arg) {
                    arg = decodeURIComponent(arg);
                    if (/^\d+$/.test(arg)) {
                        return parseInt(arg, 10);
                    } else if (arg === 'true') {
                        return true;
                    } else if (arg === 'false') {
                        return false;
                    } else {
                        return arg;
                    }
                }) : [];

            var filterInfo;
            if (filters[operationName]) {
                flushOperations();
                filterInfo = filters[operationName](operationArgs, {
                    numPreceedingFilters: filterInfos.length
                });
                if (filterInfo) {
                    filterInfo.usedQueryStringFragments = [keyValuePair];
                    filterInfo.operationName = operationName;
                    if (filterInfo.outputContentType) {
                        targetContentType = filterInfo.outputContentType;
                    }
                    filterInfos.push(filterInfo);
                    operationNames.push(operationName);
                    usedQueryStringFragments.push(keyValuePair);
                } else {
                    leftOverQueryStringFragments.push(keyValuePair);
                }
            } else if (operationName === 'metadata' && sharp) {
                flushOperations();
                targetContentType = 'application/json; charset=utf-8';
                filterInfos.push({
                    metadata: true,
                    outputContentType: targetContentType,
                    create: function () {
                        var sharpInstance = sharp();
                        var duplexStream = new Stream.Duplex();
                        duplexStream._write = function (chunk, encoding, cb) {
                            if (sharpInstance.write(chunk, encoding) === false) {
                                sharpInstance.once('drain', cb);
                            } else {
                                cb();
                            }
                        };
                        duplexStream._read = function (size) {
                            sharpInstance.metadata().then(function (metadata) {
                                if (metadata.format) {
                                    // metadata.format is one of 'jpeg', 'png', 'webp' so this should be safe:
                                    metadata.contentType = 'image/' + metadata.format;
                                }
                                _.defaults(metadata, sourceMetadata);
                                if (metadata.exif) {
                                    var exifData;
                                    try {
                                        exifData = exifReader(metadata.exif);
                                    } catch (e) {
                                        // Error: Invalid EXIF
                                    }
                                    metadata.exif = undefined;
                                    if (exifData) {
                                        _.defaults(metadata, exifData);
                                    }
                                }
                                if (metadata.icc) {
                                    try {
                                        metadata.icc = icc.parse(metadata.icc);
                                    } catch (e) {
                                        // Error: Error: Invalid ICC profile, remove the Buffer
                                        metadata.icc = undefined;
                                    }
                                }
                                duplexStream.push(JSON.stringify(metadata));
                                duplexStream.push(null);
                            }, function (err) {
                                duplexStream.emit('error', err);
                            });
                        };
                        duplexStream.on('finish', function () {
                            sharpInstance.end();
                        });
                        return duplexStream;
                    }
                });
            } else if (isOperationByEngineNameAndName[operationName]) {
                usedQueryStringFragments.push(keyValuePair);
                flushOperations();
                defaultEngineName = operationName;
            } else if (engineNamesByOperationName[operationName]) {
                // Check if at least one of the engines supporting this operation is allowed
                var candidateEngineNames = engineNamesByOperationName[operationName].filter(function (engineName) {
                    return filters[engineName] !== false;
                });
                if (candidateEngineNames.length > 0) {
                    if (currentEngineName && !isOperationByEngineNameAndName[currentEngineName]) {
                        flushOperations();
                    }

                    if (!currentEngineName || candidateEngineNames.indexOf(currentEngineName) === -1) {
                        if (candidateEngineNames.indexOf(defaultEngineName) !== -1) {
                            currentEngineName = defaultEngineName;
                        } else {
                            currentEngineName = candidateEngineNames[0];
                        }
                    }
                    var sourceContentType = targetContentType;
                    if (operationName === 'setFormat' && operationArgs.length > 0) {
                        var targetFormat = operationArgs[0].toLowerCase();
                        if (targetFormat === 'jpg') {
                            targetFormat = 'jpeg';
                        }
                        targetContentType = 'image/' + targetFormat;
                    } else if (operationName === 'jpeg' || operationName === 'png' || operationName === 'webp') {
                        targetContentType = 'image/' + operationName;
                    }
                    operations.push({sourceContentType: sourceContentType, name: operationName, args: operationArgs, usedQueryStringFragment: keyValuePair});
                    usedQueryStringFragments.push(keyValuePair);
                }
            } else {
                var operationNameLowerCase = operationName.toLowerCase(),
                    FilterConstructor = filterConstructorByOperationName[operationNameLowerCase];
                if (FilterConstructor && filters[operationNameLowerCase] !== false) {
                    operationNames.push(operationNameLowerCase);
                    flushOperations();
                    if (operationNameLowerCase === 'svgfilter' && root && options.sourceFilePath) {
                        operationArgs.push('--root', 'file://' + root, '--url', 'file://' + options.sourceFilePath);
                    }
                    filterInfo = {
                        create: function () {
                            return new FilterConstructor(operationArgs);
                        },
                        operationName: operationNameLowerCase,
                        usedQueryStringFragments: [keyValuePair]
                    };
                    filterInfos.push(filterInfo);
                    usedQueryStringFragments.push(keyValuePair);
                    if (operationNameLowerCase === 'inkscape') {
                        var filter = filterInfo.create();
                        filterInfo.create = function () {
                            return filter;
                        };
                        targetContentType = 'image/' + filter.outputFormat;
                    }
                } else {
                    leftOverQueryStringFragments.push(keyValuePair);
                }
            }
        }
    });
    flushOperations();

    return {
        targetContentType: targetContentType,
        operationNames: operationNames,
        filterInfos: filterInfos,
        usedQueryStringFragments: usedQueryStringFragments,
        leftOverQueryStringFragments: leftOverQueryStringFragments
    };
};
<|endoftext|>


<commit_before>if (global.GENTLY) require = GENTLY.hijack(require);

var util = require('util'),
    WriteStream = require('fs').WriteStream,
    EventEmitter = require('events').EventEmitter,
    crypto = require('crypto');

function File(properties) {
  EventEmitter.call(this);

  this.size = 0;
  this.path = null;
  this.name = null;
  this.type = null;
  this.hash = null;
  this.lastModifiedDate = null;

  this._writeStream = null;

  for (var key in properties) {
    this[key] = properties[key];
  }

  if(typeof this.hash === 'string') {
    this.hash = crypto.createHash(properties.hash);
  } else {
    this.hash = null;
  }
}
module.exports = File;
util.inherits(File, EventEmitter);

File.prototype.open = function() {
  this._writeStream = new WriteStream(this.path);
};

File.prototype.toJSON = function() {
  return {
    size: this.size,
    path: this.path,
    name: this.name,
    type: this.type,
    mtime: this.lastModifiedDate,
    length: this.length,
    filename: this.filename,
    mime: this.mime
  };
};

File.prototype.write = function(buffer, cb) {
  var self = this;
  if (self.hash) {
    self.hash.update(buffer);
  }
  this._writeStream.write(buffer, function() {
    self.lastModifiedDate = new Date();
    self.size += buffer.length;
    self.emit('progress', self.size);
    cb();
  });
};

File.prototype.end = function(cb) {
  var self = this;
  if (self.hash) {
    self.hash = self.hash.digest('hex');
  }
  this._writeStream.end(function() {
    self.emit('end');
    cb();
  });
};
<commit_msg>Access WriteStream of fs during runtime instead of include time<commit_after>if (global.GENTLY) require = GENTLY.hijack(require);

var util = require('util'),
    fs = require('fs'),
    EventEmitter = require('events').EventEmitter,
    crypto = require('crypto');

function File(properties) {
  EventEmitter.call(this);

  this.size = 0;
  this.path = null;
  this.name = null;
  this.type = null;
  this.hash = null;
  this.lastModifiedDate = null;

  this._writeStream = null;

  for (var key in properties) {
    this[key] = properties[key];
  }

  if(typeof this.hash === 'string') {
    this.hash = crypto.createHash(properties.hash);
  } else {
    this.hash = null;
  }
}
module.exports = File;
util.inherits(File, EventEmitter);

File.prototype.open = function() {
  this._writeStream = new fs.WriteStream(this.path);
};

File.prototype.toJSON = function() {
  return {
    size: this.size,
    path: this.path,
    name: this.name,
    type: this.type,
    mtime: this.lastModifiedDate,
    length: this.length,
    filename: this.filename,
    mime: this.mime
  };
};

File.prototype.write = function(buffer, cb) {
  var self = this;
  if (self.hash) {
    self.hash.update(buffer);
  }
  this._writeStream.write(buffer, function() {
    self.lastModifiedDate = new Date();
    self.size += buffer.length;
    self.emit('progress', self.size);
    cb();
  });
};

File.prototype.end = function(cb) {
  var self = this;
  if (self.hash) {
    self.hash = self.hash.digest('hex');
  }
  this._writeStream.end(function() {
    self.emit('end');
    cb();
  });
};
<|endoftext|>


<commit_before>var express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors');
var request = require('request');
var events = require('events');
var fh = require('fh-mbaas-api');
var _ = require('underscore');

var fuseRoute = function() {
  var fuse = new express.Router();
  fuse.use(cors());
  fuse.use(bodyParser());

  fuse.post('/', function(req, res) {
    console.log(new Date(), 'In fuse route POST / req.body=', req.body);
    return submitToFuse(req.body, function(error, response, body){
      return res.send(body);
    });
  });

  return fuse;
};

var submitToFuse = function(submission, cb){
  var data = transform(submission);
  var url = '';
  if (submission.formId == '5616b01a62af16be309b500a' )
    url = process.env.FUSE_REGISTER_URL || 'http://85.190.180.161:8182/cxf/redhat/forum/register';
  else if(submission.formId == '5616b26ca32871323c63964d' ){
    url = process.env.FUSE_CHECKIN_URL || 'http://85.190.180.161:8182/cxf/redhat/forum/checkin';
  }
  else {
    cb(new Error("not valid"));
  }
  var options = { "url" : url, "body": data, "json": true};
  request.post(options, function (error, response, body) {
    if (error) {
      console.log(error) // Show the error
    }
    cb(error, response, body);
  });
};

var mapping = {
  "FFIRSTNAME" : "name",
  "FLASTNAME" : "lastName",
  "FEMAIL" : "email",
  "FPICTURE" : "picture",
  "FCOMPANY" : "companyName",
  "FTITLE" : "jobTitle",
  "FWEB" : "web",
  "FCODE" : "standCode",
  "FCOMMENTS" : "comments"
};

var transform = function(submission) {
  var map = {};
  _.each(submission.submission.formFields, function(item){
    //console.log(JSON.stringify(item));
    map[mapping[item.fieldId.fieldCode]] = item.fieldValues[0];
  });
  console.log(JSON.stringify(map));
  return map;
}

var submissionEventListener = new events.EventEmitter();

submissionEventListener.on('submissionComplete', function(submission){
  console.log('New submission received: ');
  //console.log(submission);
  return submitToFuse(submission, function(error, response, body){
      console.log('Received response from Fuse in event handler: ' + response);
      console.log(body);
  });
});

fh.forms.registerListener(submissionEventListener, function(err){});

module.exports = fuseRoute;<commit_msg>update file /lib/fuse.js<commit_after>var express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors');
var request = require('request');
var events = require('events');
var fh = require('fh-mbaas-api');
var _ = require('underscore');

var fuseRoute = function() {
  var fuse = new express.Router();
  fuse.use(cors());
  fuse.use(bodyParser());

  fuse.post('/', function(req, res) {
    console.log(new Date(), 'In fuse route POST / req.body=', req.body);
    return submitToFuse(req.body, function(error, response, body){
      return res.send(body);
    });
  });

  return fuse;
};

var submitToFuse = function(submission, cb){
  var data = transform(submission);
  var url = '';
  console.log(submission);
  if (submission.formId == '5616b01a62af16be309b500a' )
    url = process.env.FUSE_REGISTER_URL || 'http://85.190.180.161:8182/cxf/redhat/forum/register';
  else if(submission.formId == '5616b26ca32871323c63964d' ){
    url = process.env.FUSE_CHECKIN_URL || 'http://85.190.180.161:8182/cxf/redhat/forum/checkin';
  }
  else {
    cb(new Error("not valid"));
  }
  var options = { "url" : url, "body": data, "json": true};
  request.post(options, function (error, response, body) {
    if (error) {
      console.log(error) // Show the error
    }
    cb(error, response, body);
  });
};

var mapping = {
  "FFIRSTNAME" : "name",
  "FLASTNAME" : "lastName",
  "FEMAIL" : "email",
  "FPICTURE" : "picture",
  "FCOMPANY" : "companyName",
  "FTITLE" : "jobTitle",
  "FWEB" : "web",
  "FCODE" : "standCode",
  "FCOMMENTS" : "comments"
};

var transform = function(submission) {
  var map = {};
  _.each(submission.submission.formFields, function(item){
    //console.log(JSON.stringify(item));
    map[mapping[item.fieldId.fieldCode]] = item.fieldValues[0];
  });
  console.log(JSON.stringify(map));
  return map;
}

var submissionEventListener = new events.EventEmitter();

submissionEventListener.on('submissionComplete', function(submission){
  console.log('New submission received: ');
  //console.log(submission);
  return submitToFuse(submission, function(error, response, body){
      console.log('Received response from Fuse in event handler: ' + response);
      console.log(body);
  });
});

fh.forms.registerListener(submissionEventListener, function(err){});

module.exports = fuseRoute;<|endoftext|>


<commit_before>var $ = require('jquery');
$(document).ready(function () {
var pets = ['dog.png','dog.png','dolphin.png','dolphin.png', 'cat.png', 'cat.png', 'capybara.png', 'capybara.png'];
//var pets = ['dolphin','dolphin','dolphin','dolphin'];
var selectedCards = [];

var petsDeck = document.getElementById('pets');
  petsDeck.addEventListener('click', function() {
    var board = new Board(pets);
    board.render();
    bindCardListeners(board);
  });
  // change the card visible attribute to be true or false
  // then have the board re-render
  // a card was clicked; can we tell the board to flip
  // the appropriate card for that... index? x/y?
function bindCardListeners(board) {
  var cards = document.querySelectorAll('.card')
  board.deck_for_game.forEach( function(card, index) {
    var card_div = document.querySelector('#card_' + index)
    card_div.addEventListener('click', function() {
      console.log(card.visible);
      flipCard(card_div, card);
    });
  });
};

function Card(url, index) {
  this.image = url
  this.visible = false
  this.index = index
}

Board.prototype.shuffle = function() {
  for(var j, x, i = this.deck.length; i; j = Math.floor(Math.random() * i), x = this.deck[--i], this.deck[i] = this.deck[j], this.deck[j] = x);
    return this.deck;
}

function Board(array) {
  this.deck = array
  this.deck_for_game = [];
}

function flipCard(div, card) {
  if (selectedCards.length === 0) {
    $(div).css('background-image', 'url(images/' + card.image + ')');
    card.visible = true
    selectedCards.push(div, card);
    console.log(selectedCards);
  } else if (selectedCards.length === 2) {
    $(div).css('background-image', 'url(images/' + card.image + ')');
    card.visible = true
    selectedCards.push(div, card)

    if (selectedCards[1].card.image == selectedCards[3].card.image) {
      //matched_cards += 2;
      selectedCards = [];
    } else {
      setTimeout(function() {
        selectedCards[0].css('background-image', '');
        selectedCards[1].css('background-image', '');
        selectedCards = [];
      }, 800);
    }
  }

  //if (selectedCards < 2) {
  //  selectedCards.push(div, card)
  //} else if (selectedCards.length === 2) {
  //  if (selectedCards[0].second.card.image === selectedCards[1].second.card.image) {
  //    selectedCards = [];
  //  } else {
  //    setTimeout(function() {
  //      selectedCards[0].div.css('background-image', '');
  //      selectedCards[1].div.css('background-image', '');
  //      selectedCards = [];
  //    }, 800);
  //  }
  //};
};

Board.prototype.render = function() {
  this.shuffle();
  for(var i = 0; i < this.deck.length; i++) {
    var card = new Card(this.deck[i], i);
    this.deck_for_game.push(card);
    var deck = '

';
    document.getElementById('memory_board').innerHTML += deck;
  }
}
});
//Card -- image, flipped(visible or hidden)
//Deck -- stores cards, shuffle
//BoardGames -- renders deck, matches cards (num of matched cards === num of cards in deck)
//Game -- renders board, determine if game won?, number of clicks
<commit_msg>Able to match cards, visible cards are unclickable...jankiest function ever<commit_after>var $ = require('jquery');
$(document).ready(function () {
var pets = ['dog.png','dog.png','dolphin.png','dolphin.png', 'cat.png', 'cat.png', 'capybara.png', 'capybara.png'];
//var pets = ['dolphin','dolphin','dolphin','dolphin'];
var selectedCards = [];

var petsDeck = document.getElementById('pets');
  petsDeck.addEventListener('click', function() {
    var board = new Board(pets);
    board.render();
    bindCardListeners(board);
  });
  // change the card visible attribute to be true or false
  // then have the board re-render
  // a card was clicked; can we tell the board to flip
  // the appropriate card for that... index? x/y?
function bindCardListeners(board) {
  var cards = document.querySelectorAll('.card')
  board.deck_for_game.forEach( function(card, index) {
    var card_div = document.querySelector('#card_' + index)
    card_div.addEventListener('click', function() {
      if (card.visible === false) {
        flipCard(card_div, card);
      }
    });
  });
};

function Card(url, index) {
  this.image = url
  this.visible = false
  this.index = index
}

Board.prototype.shuffle = function() {
  for(var j, x, i = this.deck.length; i; j = Math.floor(Math.random() * i), x = this.deck[--i], this.deck[i] = this.deck[j], this.deck[j] = x);
    return this.deck;
}

function Board(array) {
  this.deck = array
  this.deck_for_game = [];
}

function flipCard(div, card) {
  if (selectedCards.length === 0) {
    $(div).css('background-image', 'url(images/' + card.image + ')');
    card.visible = true;
    selectedCards.push(div, card);
    console.log(selectedCards);
  } else if (selectedCards.length === 2) {
    $(div).css('background-image', 'url(images/' + card.image + ')');
    card.visible = true;
    selectedCards.push(div, card);

    if (selectedCards[1].image === selectedCards[3].image) {
      //matchedCards += 2;
      selectedCards = [];
    } else {
      setTimeout(function() {
        $(selectedCards[0]).css('background-image', '');
        selectedCards[1].visible = false;
        $(selectedCards[2]).css('background-image', '');
        selectedCards[3].visible = false;
        selectedCards = [];
      }, 800);
    }
  }

  //if (selectedCards < 2) {
  //  selectedCards.push(div, card)
  //} else if (selectedCards.length === 2) {
  //  if (selectedCards[0].second.card.image === selectedCards[1].second.card.image) {
  //    selectedCards = [];
  //  } else {
  //    setTimeout(function() {
  //      selectedCards[0].div.css('background-image', '');
  //      selectedCards[1].div.css('background-image', '');
  //      selectedCards = [];
  //    }, 800);
  //  }
  //};
};

Board.prototype.render = function() {
  this.shuffle();
  for(var i = 0; i < this.deck.length; i++) {
    var card = new Card(this.deck[i], i);
    this.deck_for_game.push(card);
    var deck = '

';
    document.getElementById('memory_board').innerHTML += deck;
  }
}
});
//Card -- image, flipped(visible or hidden)
//Deck -- stores cards, shuffle
//BoardGames -- renders deck, matches cards (num of matched cards === num of cards in deck)
//Game -- renders board, determine if game won?, number of clicks
<|endoftext|>


<commit_before>var $ = require('jQuery');
var canvas  = document.getElementById('game');
var context = canvas.getContext('2d');
var Board   = require('./board.js');
var jShape  = require('./game-pieces/j-shape.js');
var oShape = require('./game-pieces/o-shape.js');
var iShape = require('./game-pieces/i-shape.js');
var lShape = require('./game-pieces/L-shape.js');
var tShape = require('./game-pieces/t-shape.js');
var sShape = require('./game-pieces/s-shape.js');
var zShape = require('./game-pieces/z-shape.js');
var _ = require('lodash');

var gameBoard = new Board();

var currentShape = generateShape();

function generateShape () {
  var shapes    = [jShape, oShape, iShape, lShape, tShape, sShape, zShape];
  var shape = _.sample(shapes);

  return new shape(gameBoard);
}

requestAnimationFrame(function gameLoop() {
  if (!currentShape) {
    generateShape();
  }

  context.clearRect(0, 0, canvas.width, canvas.height);
  gameBoard.draw(context);

  if (!currentShape.canMoveDown()) {
    _.each(currentShape.pieces, function(piece) {
      piece.locked = true;
    });

    gameBoard.checkRows();
    currentShape = generateShape();
  }

  currentShape.moveDown();

  setTimeout(function() {
    requestAnimationFrame(gameLoop);
  }, 500);
});

// Key Bindings
$(document).keydown(function(event) {
  switch(event.which) {
    case 37: // left
    currentShape.moveLeft();
    resetBoard(); // Refresh board if piece moves
    break;

    case 38: // up
    currentShape.rotate();
    resetBoard();
    break;

    case 39: // right
    currentShape.moveRight();
    resetBoard();
    break;

    case 40: // down
    currentShape.moveDown();
    resetBoard();
    break;

      default: return; // exit this handler for other keys
  }
    event.preventDefault(); // prevent the default action (scroll / move caret)
});

function resetBoard () {
  context.clearRect(0, 0, canvas.width, canvas.height);
  gameBoard.draw(context);
}<commit_msg>I don't remember<commit_after>var $ = require('jQuery');
var canvas  = document.getElementById('game');
var context = canvas.getContext('2d');
var Board   = require('./board.js');
var jShape  = require('./game-pieces/j-shape.js');
var oShape = require('./game-pieces/o-shape.js');
var iShape = require('./game-pieces/i-shape.js');
var lShape = require('./game-pieces/L-shape.js');
var tShape = require('./game-pieces/t-shape.js');
var sShape = require('./game-pieces/s-shape.js');
var zShape = require('./game-pieces/z-shape.js');
var _ = require('lodash');

var gameBoard = new Board();

var currentShape = generateShape();

// Shape Generator
function generateShape () {
  var shapes    = [jShape, oShape, iShape, lShape, tShape, sShape, zShape];
  var shape = _.sample(shapes);

  return new shape(gameBoard);
}

requestAnimationFrame(function gameLoop() {
  if (!currentShape) {
    generateShape();
  }

  context.clearRect(0, 0, canvas.width, canvas.height);
  gameBoard.draw(context);

  // lock pieces when they hit the bottom
  if (!currentShape.canMoveDown()) {
    _.each(currentShape.pieces, function(piece) {
      piece.locked = true;
    });

    gameBoard.checkRows();
    currentShape = generateShape();
  }

  currentShape.moveDown();

  setTimeout(function() {
    requestAnimationFrame(gameLoop);
  }, 500);
});

// Key Bindings
$(document).keydown(function(event) {
  switch(event.which) {
    case 37: // left
    currentShape.moveLeft();
    resetBoard(); // Refresh board if piece moves
    break;

    case 38: // up
    currentShape.rotate();
    resetBoard();
    break;

    case 39: // right
    currentShape.moveRight();
    resetBoard();
    break;

    case 40: // down
    currentShape.moveDown();
    resetBoard();
    break;

      default: return; // exit this handler for other keys
  }
    event.preventDefault(); // prevent the default action (scroll / move caret)
});

function resetBoard () {
  context.clearRect(0, 0, canvas.width, canvas.height);
  gameBoard.draw(context);
}<|endoftext|>


<commit_before>const gpio = require('pi-gpio');

gpio.open(17, 'output', (err) => { // Open pin 17 for output
	gpio.write(17, 1, () => { // Set pin 17 high (1)
		gpio.close(17); // Close pin 17
	});
});
<commit_msg>Update<commit_after>const gpio = require('pi-gpio');

let pin = 17;

console.log('['+pin+'] Open for output');
gpio.open(pin, 'output', (err) => { // Open pin for output
	console.log('['+pin+'] Set high');
	gpio.write(pin, 1, () => { // Set pin high (1)
		console.log('['+pin+'] Close');
		gpio.close(pin); // Close pin
	});
});
<|endoftext|>


<commit_before>var Grid = function(items, options) {
  /**
   * A Grid manages a sorted list of rectangular items with variable width and
   * height.
   *
   * The Grid has two functions:
   *
   *  - Generate the 2d position based on the sorting index
   *  - TODO: Generate the sorting index based on the 2d position
   *
   * The 2d position is generated under the constraints of N rows and M columns
   * per Group (see below). The virtual Grid space will expand horizontally to
   * any size required to fit all the items provided.
   *
   * A Group is a positional subset of the Grid, of variable columns. These are
   * the specs of the positioning algorithm:
   *
   *  - Groups are displayed one after another, from left to right.
   *  - Items withing a group are displayed left-to-right, top-to-bottom
   *  - If an item doesn't fit to the right of the previous item within the
   *    current Group, it will go under it
   *  - If an item doesn't fit under the previous item within the current
   *    Group, it will start a new Group, where it will be placed top-left
   *
   *  _G1____   _G2____
   * |_1_|_2_| |_5_|_6_|
   * |_3_|_4_| |_7_|
   *
   * The width and height of an item is expressed using the number of cols and
   * rows it takes un within the grid.
   *
   * Each item has a numerical sorting "index" and a 2d "position" within the
   * grid. At least one of them is required. One can be calculated from the
   * other.
   *
   * An item is an object of structure:
   * {
   *   cols: 3,
   *   rows: 1,
   *   index: 3,
   *   position: [0, 2]
   * }
   */
  this.options = $.extend({}, this.defaults, options || {});
  this.items = items;
  this.groups = [];
  this.startNewGroup();
};

Grid.prototype.defaults = {
  rows: 5,
  colsPerGroup: 5
};

Grid.prototype.generatePositionsFromIndex = function() {
  this._sortItemsByIndex();
  for (var i = 0; i < this.items.length; i++) {
    this._generateItemPositionFromIndex(this.items[i]);
  }
  return this.items;
};

Grid.prototype._generateItemPositionFromIndex = function(item) {
  var currentGroup = this.getCurrentGroup();
  var itemPosition = currentGroup.findPositionForItem(item);
  // Warning: Any item must fit in a new blank group, otherwise this will go
  // into an infinite recursion loop
  if (itemPosition) {
    item.position = itemPosition;
    currentGroup.markItemCells(item);
    // Apply offset of cols based on the number of Groups before the current
    // one, to make the item position relative to the entire grid
    item.position[0] += this.groups.indexOf(currentGroup) *
                        this.options.colsPerGroup;
  } else {
    this.startNewGroup();
    this._generateItemPositionFromIndex(item);
  }
};

Grid.prototype._sortItemsByIndex = function() {
  this.items.sort(function(item1, item2) {
    return item1.index - item2.index;
  });
};

Grid.prototype.startNewGroup = function() {
  this.groups.push(new GridGroup(this.options.colsPerGroup, this.options.rows));
};

Grid.prototype.getCurrentGroup = function() {
  return this.groups[this.groups.length - 1];
};

var GridGroup = function(cols, rows) {
  this.colsPerRow = cols;
  // Create empty rows
  for (var i = 0; i < rows; i++) {
    this.push(new GridRow(cols));
  }
};

// Extend the Array prototype
GridGroup.prototype = [];

GridGroup.prototype.findPositionForItem = function(item) {
  var x, y, row;
  for (y = 0; y < this.length; y++) {
    row = this[y];
    for (x = 0; x < row.length; x++) {
      if (this.itemFitsAtPosition(item, [x, y])) {
        return [x, y];
      }
    }
  }
  // There isn't available space for item in this Group
  return null;
};

GridGroup.prototype.itemFitsAtPosition = function(item, position) {
  /**
   * Check that an item wouldn't overlap with another one if placed at a
   * certain position within the grid
   */
  var x, y, row;
  // Make sure the item doesn't go outside of the grid bounds
  if ((position[0] + item.cols > this.colsPerRow) ||
      (position[1] + item.rows > this.length)) {
    return false;
  }
  // Make sure the item doesn't overlap with an already positioned item, or
  // isn't placed before a previous item
  for (y = position[1]; y < this.length; y++) {
    row = this[y];
    for (x = position[0]; x < row.length; x++) {
      if (row[x]) {
        return false;
      }
    }
  }
  return true;
};

GridGroup.prototype.markItemCells = function(item) {
  /**
   * Mark the grid cells that are occupied by an item. This prevents items
   * from overlapping in the grid
   */
  var x, y, row;
  for (y = item.position[1]; y < item.position[1] + item.rows; y++) {
    row = this[y];
    for (x = item.position[0]; x < item.position[0] + item.cols; x++) {
      row[x] = item;
    }
  }
};

var GridRow = function(cols) {
  for (var i = 0; i < cols; i++) {
    this.push(null);
  }
};

// Extend the Array prototype
GridRow.prototype = [];
<commit_msg>Begin implementation of sorting items based on 2d position<commit_after>var Grid = function(items, options) {
  /**
   * A Grid manages a sorted list of rectangular items with variable width and
   * height.
   *
   * The Grid has two functions:
   *
   *  - Generate the 2d position based on the sorting index
   *  - TODO: Generate the sorting index based on the 2d position
   *
   * The 2d position is generated under the constraints of N rows and M columns
   * per Group (see below). The virtual Grid space will expand horizontally to
   * any size required to fit all the items provided.
   *
   * A Group is a positional subset of the Grid, of variable columns. These are
   * the specs of the positioning algorithm:
   *
   *  - Groups are displayed one after another, from left to right.
   *  - Items withing a group are displayed left-to-right, top-to-bottom
   *  - If an item doesn't fit to the right of the previous item within the
   *    current Group, it will go under it
   *  - If an item doesn't fit under the previous item within the current
   *    Group, it will start a new Group, where it will be placed top-left
   *
   *  _G1____   _G2____
   * |_1_|_2_| |_5_|_6_|
   * |_3_|_4_| |_7_|
   *
   * The width and height of an item is expressed using the number of cols and
   * rows it takes un within the grid.
   *
   * Each item has a numerical sorting "index" and a 2d "position" within the
   * grid. At least one of them is required. One can be calculated from the
   * other.
   *
   * An item is an object of structure:
   * {
   *   cols: 3,
   *   rows: 1,
   *   index: 3,
   *   position: [0, 2]
   * }
   */
  this.options = $.extend({}, this.defaults, options || {});
  this.items = items;
  this.groups = [];
  this.startNewGroup();
};

Grid.prototype.defaults = {
  rows: 5,
  colsPerGroup: 5
};

Grid.prototype.generatePositionsFromIndex = function() {
  this._sortItemsByIndex();
  for (var i = 0; i < this.items.length; i++) {
    this._generateItemPositionFromIndex(this.items[i]);
  }
  return this.items;
};

Grid.prototype.generateItemIndexFromPosition = function(item) {
  // TODO: Test and implement
  this._sortItemsByPosition();
};

Grid.prototype._generateItemPositionFromIndex = function(item) {
  var currentGroup = this.getCurrentGroup();
  var itemPosition = currentGroup.findPositionForItem(item);
  // Warning: Any item must fit in a new blank group, otherwise this will go
  // into an infinite recursion loop
  if (itemPosition) {
    item.position = itemPosition;
    currentGroup.markItemCells(item);
    // Apply offset of cols based on the number of Groups before the current
    // one, to make the item position relative to the entire grid
    item.position[0] += this.groups.indexOf(currentGroup) *
                        this.options.colsPerGroup;
  } else {
    this.startNewGroup();
    this._generateItemPositionFromIndex(item);
  }
};

Grid.prototype._sortItemsByIndex = function() {
  this.items.sort(function(item1, item2) {
    return item1.index - item2.index;
  });
};

Grid.prototype._sortItemsByPosition = function() {
  // FIXME: wip, not 100% correct
  this.items.sort(function(item1, item2) {
    if (item1.position[0] !== item2.position[0]) {
      return item1.position[0] - item2.position[1];
    } else {
      return item1.position[1] - item2.position[1];
    }
  });
};

Grid.prototype.startNewGroup = function() {
  this.groups.push(new GridGroup(this.options.colsPerGroup, this.options.rows));
};

Grid.prototype.getCurrentGroup = function() {
  return this.groups[this.groups.length - 1];
};

var GridGroup = function(cols, rows) {
  this.colsPerRow = cols;
  // Create empty rows
  for (var i = 0; i < rows; i++) {
    this.push(new GridRow(cols));
  }
};

// Extend the Array prototype
GridGroup.prototype = [];

GridGroup.prototype.findPositionForItem = function(item) {
  var x, y, row;
  for (y = 0; y < this.length; y++) {
    row = this[y];
    for (x = 0; x < row.length; x++) {
      if (this.itemFitsAtPosition(item, [x, y])) {
        return [x, y];
      }
    }
  }
  // There isn't available space for item in this Group
  return null;
};

GridGroup.prototype.itemFitsAtPosition = function(item, position) {
  /**
   * Check that an item wouldn't overlap with another one if placed at a
   * certain position within the grid
   */
  var x, y, row;
  // Make sure the item doesn't go outside of the grid bounds
  if ((position[0] + item.cols > this.colsPerRow) ||
      (position[1] + item.rows > this.length)) {
    return false;
  }
  // Make sure the item doesn't overlap with an already positioned item, or
  // isn't placed before a previous item
  for (y = position[1]; y < this.length; y++) {
    row = this[y];
    for (x = position[0]; x < row.length; x++) {
      if (row[x]) {
        return false;
      }
    }
  }
  return true;
};

GridGroup.prototype.markItemCells = function(item) {
  /**
   * Mark the grid cells that are occupied by an item. This prevents items
   * from overlapping in the grid
   */
  var x, y, row;
  for (y = item.position[1]; y < item.position[1] + item.rows; y++) {
    row = this[y];
    for (x = item.position[0]; x < item.position[0] + item.cols; x++) {
      row[x] = item;
    }
  }
};

var GridRow = function(cols) {
  for (var i = 0; i < cols; i++) {
    this.push(null);
  }
};

// Extend the Array prototype
GridRow.prototype = [];
<|endoftext|>


<commit_before>var Q = require('q');
var async = require('async');
var url = require('url');
var cheerio = require('cheerio');
var fs = require('fs');

module.exports = {
    /**
     * Iterates through a set of HTML resources, to check for matches of given
     * CSS selectors.
     *
     * @param {Array}  pages HTML pages to search through. This can be either an
     *                 array of Strings (containing html code), an array of URIs
     *                 to visit, or an array of paths to html files.
     * @param {Array}  selectors Array of CSS selectors to match
     * @param {String} cookie Cookie to use for login, on the form
     *                 "sessionid=foo". Each uri in the html parameter will
     *                 be visited both with and without the cookie set.
     * @param {Array}  whitelist List of selectors to ignore.
     * @returns {Promise} Object on the form { selectors: [".foo", "#bar"] },
     *                                         duplicates: { ".foo": 2 } }
     */
    matchSelectors: function(pages, selectors, cookie, whitelist) {
        var result = { used: {}, ignored: {} };
        var deferred = Q.defer();
        var processed = [];
        var i, l;

        // Handle excludes by adding them to processed, so they won't be visited
        var excludedSubdomains = [];
        if (pages.exclude) {
            for (i=0, l=pages.exclude.length; i<l; i++) {
                var current = pages.exclude[i];
                if (current.indexOf("*") !== -1) {
                    var subdomain = current.substring(0, current.length - 1);
                    if (subdomain.indexOf("http") === 0) {
                        subdomain = url.parse(subdomain).pathname;
                    }
                    excludedSubdomains.push(subdomain);
                    continue;
                }

                processed.push(current.split("?")[0]);
            }
        }

        var self = this;
        var queue = async.queue(function(item, queueCallback) {
            // If in processed, skip (may have been in excluded list)
            if (-1 !== processed.indexOf(item.page.split("?")[0])) {
                queueCallback();
                return;
            }

            var page = item.page;
            var uri, host;
            if (0 === page.indexOf("http")) {
//                console.log("Visiting URL: ", item.page.split("?")[0]);
                uri = page;
                host = url.parse(uri).host || "";
            }

            var pagesToVisit = [];
            pagesToVisit.push(self._getHtmlAsString(page, null)); // regular visit
            if (cookie) {
                pagesToVisit.push(self._getHtmlAsString(page, cookie)); // logged in visit
            }
            processed.push(page.split("?")[0]);

            Q.all(pagesToVisit)
            .spread(function(regularResult, loggedInResult) {
                var context = {
                    uri: uri,
                    followLinks: item.followLinks,
                    selectors: selectors,
                    whitelist: whitelist,
                    excludedSubdomains: excludedSubdomains,
                    result: result
                };

                if (regularResult) {
                    self._processHtmlString(regularResult, context, queue);
                }

                if (loggedInResult) {
                    self._processHtmlString(loggedInResult, context, queue);
                }
            }).fail(function(error) {
                console.log(error);
            }).done(queueCallback);
        }, 8);

        queue.drain = function(err) { // TODO: Handle err
            if (err) {
                deferred.reject(new Error(err));
            } else {
                deferred.resolve(result);
            }
        };

        // Crawl to find all HTML links
        if (pages.crawl) {
            for (i=0, l=pages.crawl.length; i<l; i++) {
                queue.push({page: pages.crawl[i], followLinks: true});
            }
        }

        if (pages.include) {
            for (i=0, l=pages.include.length; i<l; i++) {
                queue.push({page: pages.include[i], followLinks: false});
            }
        }

        return deferred.promise;
    },

    /**
     * Takes a HTML string and matches CSS selectors in it, and adds/updates
     * them to the result object given in the context parameter.
     *
     * If context.followLinks is true, found links are added to queue so that
     * they will be processed as well.
     *
     * @private
     * @param {String}  html Html to search through.
     * @param {Object}  context An object containing the context information:
     * @param {String}  context.uri Original URI to the html string
     * @param {Boolean} context.followLinks If links in html string should be followed
     * @param {Array}   context.selectors List of selectors to match
     * @param {Array}   context.whitelist List of rules to ignore
     * @param {Array}   context.excludedSubdomains List of subdomains to ignore
     * @param {Object}  context.result Object to append results to
     * @param {Object}  context.result.used E.g. {".foo": 1 }
     * @param {Object}  context.result.ignored E.g. {"@font-face": 1}
     * @param {Object}  queue Queue object, as returned from the Async library.
     */
    _processHtmlString: function(html, context, queue) {
        var uri = context.uri;
        var followLinks = context.followLinks;
        var selectors = context.selectors;
        var whitelist = context.whitelist;
        var result = context.result;
        var excludedSubdomains = context.excludedSubdomains;

        var document = cheerio.load(html);

        if (followLinks) { // look for links in document, add to queue
            var links = document("a");
            for (var i=0, l=links.length; i<l; i++) {
                var handleThis = true;

                var href = links[i].attribs.href.split("#")[0];

                // No href, or current URI with parameter, continue
                if (!href || href.indexOf("?") === 0) {
                    continue;
                }

                // If under excluded domain, skip
                for (var j=0; j<excludedSubdomains.length; j++) {
                    var excluded = excludedSubdomains[j];
                    if (href.indexOf(excluded) === 0 ||
                        url.parse(href).pathname.indexOf(excluded) === 0) {

                        handleThis = false;
                        break;
                    }
                }

                if (handleThis) {
                    this._addLinkToQueue(links[i].attribs.href, queue, uri, followLinks);
                }
            }
        }

        // Process current document
        return this._matchSelectorsInString(document, selectors, whitelist, result);
    },

    /**
     * Add link to queue (but only if it's on the same host as parentUri).
     *
     * @private
     * @param {String}  link Link to handle.
     * @param {Object}  queue Queue object, as returned from the Async library.
     * @param {String}  parentUri URL to document containing this link.
     * @param {Boolean} followLinks Should links in the document link refers to
     *                  be followed?
     */
    _addLinkToQueue: function(link, queue, parentUri, followLinks) {
        var host = url.parse(parentUri).host;
        if (0 === link.indexOf("http")) { // Absolute url
            if (url.parse(link).host === host) {
                queue.push({page: link, followLinks: followLinks});
            } else {
                // Skip, another domain
            }
        } else { // Relative url?
            if (parentUri) { // Yes
                link = url.resolve(parentUri, link);
                queue.push({page: link, followLinks: followLinks});
            } else { // What Is This Thing Called Link?
                console.log("Could not resolve " + link);
            }
        }
    },

    /**
     * Match selectors in a html string.
     *
     * @private
     * @param {String} htmlString HTML string
     * @param {Array}  selectors List of selectors to match
     * @param {Array}  whitelist List of selectors to ignore
     * @param {Object} result Object to append results to
     * @param {Object} result.used E.g. {".foo": 1 }
     * @param {Object} result.ignored E.g. {"@font-face": 1}
     */
    _matchSelectorsInString: function(htmlString, selectors, whitelist, result) {
        // Loop through selectors
        for (var k=0, l=selectors.length; k<l; k++) {
            var selector = selectors[k];

            // If current selector is whitelisted, skip.
            if (whitelist && -1 < whitelist.indexOf(selector)) {
                continue;
            }
            if (-1 < selector.indexOf("@")) {
                result.ignored[selector] = (result.ignored[selector] || 0) + 1;
                continue;
            }

            if (selector) {
                var oSelector = selector;

                // Add selector to index, if not already added
                if (undefined === result.used[oSelector]) {
                    result.used[oSelector] = 0;
                }

                // Remove pseudo part of selector
                selector = selector.split(":")[0];

                // Check if selector is used
                try {
                    if (htmlString(selector).length > 0) {
                        result.used[oSelector] =
                            result.used[oSelector]
                            + htmlString(selector).length;
                    }
                } catch (e) {
                    console.log("Problem with selector: "
                                + oSelector);
                }
            }
        }
        return;
    },

    /**
     * Match selectors in a html string.
     *
     * @private
     * @param {String} page URL, path or HTML as string.
     * @param {Array}  cookie Cookie to use for login, on the form
     *                 "sessionid=foo". Each uri in the html parameter will
     *                 be visited both with and without the cookie set.
     * @returns {Promise} HTML as string
     */
    _getHtmlAsString: function(page, cookie) {
        var deferred = Q.defer();
        var data;

        // Get page as raw html
        // If URI is given, fetch HTML
        //
        // Note: _addLinkToQueue adds host etc. to relative URLs when crawling.
        if (0 === page.indexOf("http")) { // From URI
            var uri = page;

            var headers = {};
            if (cookie) {
                headers = {
                    "Cookie": cookie,
                    "Referer": uri
                };
            }

            var options = { uri: uri,
                            headers: headers };

            require('request').get(options, function(error, res, data) {
                                       // TODO: Error checking, response status code, etc.
                                       deferred.resolve(data);
                                   });
        } else if (-1 === page.indexOf("<html>")) { // From file
            try {
                data = fs.readFileSync(page).toString();
            } catch (e) {
                console.log(e.message);
            }
            deferred.resolve(data);
        } else { // From string
            deferred.resolve(page);
        }

        return deferred.promise;
    }
};
<commit_msg>html.js: include context in log message when getHtmlAsString fails<commit_after>var Q = require('q');
var async = require('async');
var url = require('url');
var cheerio = require('cheerio');
var fs = require('fs');

module.exports = {
    /**
     * Iterates through a set of HTML resources, to check for matches of given
     * CSS selectors.
     *
     * @param {Array}  pages HTML pages to search through. This can be either an
     *                 array of Strings (containing html code), an array of URIs
     *                 to visit, or an array of paths to html files.
     * @param {Array}  selectors Array of CSS selectors to match
     * @param {String} cookie Cookie to use for login, on the form
     *                 "sessionid=foo". Each uri in the html parameter will
     *                 be visited both with and without the cookie set.
     * @param {Array}  whitelist List of selectors to ignore.
     * @returns {Promise} Object on the form { selectors: [".foo", "#bar"] },
     *                                         duplicates: { ".foo": 2 } }
     */
    matchSelectors: function(pages, selectors, cookie, whitelist) {
        var result = { used: {}, ignored: {} };
        var deferred = Q.defer();
        var processed = [];
        var i, l;

        // Handle excludes by adding them to processed, so they won't be visited
        var excludedSubdomains = [];
        if (pages.exclude) {
            for (i=0, l=pages.exclude.length; i<l; i++) {
                var current = pages.exclude[i];
                if (current.indexOf("*") !== -1) {
                    var subdomain = current.substring(0, current.length - 1);
                    if (subdomain.indexOf("http") === 0) {
                        subdomain = url.parse(subdomain).pathname;
                    }
                    excludedSubdomains.push(subdomain);
                    continue;
                }

                processed.push(current.split("?")[0]);
            }
        }

        var self = this;
        var queue = async.queue(function(item, queueCallback) {
            // If in processed, skip (may have been in excluded list)
            if (-1 !== processed.indexOf(item.page.split("?")[0])) {
                queueCallback();
                return;
            }

            var page = item.page;
            var uri, host;
            if (0 === page.indexOf("http")) {
//                console.log("Visiting URL: ", item.page.split("?")[0]);
                uri = page;
                host = url.parse(uri).host || "";
            }

            var pagesToVisit = [];
            pagesToVisit.push(self._getHtmlAsString(page, null)); // regular visit
            if (cookie) {
                pagesToVisit.push(self._getHtmlAsString(page, cookie)); // logged in visit
            }
            processed.push(page.split("?")[0]);

            Q.all(pagesToVisit)
            .spread(function(regularResult, loggedInResult) {
                var context = {
                    uri: uri,
                    followLinks: item.followLinks,
                    selectors: selectors,
                    whitelist: whitelist,
                    excludedSubdomains: excludedSubdomains,
                    result: result
                };

                if (regularResult) {
                    self._processHtmlString(regularResult, context, queue);
                }

                if (loggedInResult) {
                    self._processHtmlString(loggedInResult, context, queue);
                }
            }).fail(function(error) {
                console.log(error);
            }).done(queueCallback);
        }, 8);

        queue.drain = function(err) { // TODO: Handle err
            if (err) {
                deferred.reject(new Error(err));
            } else {
                deferred.resolve(result);
            }
        };

        // Crawl to find all HTML links
        if (pages.crawl) {
            for (i=0, l=pages.crawl.length; i<l; i++) {
                queue.push({page: pages.crawl[i], followLinks: true});
            }
        }

        if (pages.include) {
            for (i=0, l=pages.include.length; i<l; i++) {
                queue.push({page: pages.include[i], followLinks: false});
            }
        }

        return deferred.promise;
    },

    /**
     * Takes a HTML string and matches CSS selectors in it, and adds/updates
     * them to the result object given in the context parameter.
     *
     * If context.followLinks is true, found links are added to queue so that
     * they will be processed as well.
     *
     * @private
     * @param {String}  html Html to search through.
     * @param {Object}  context An object containing the context information:
     * @param {String}  context.uri Original URI to the html string
     * @param {Boolean} context.followLinks If links in html string should be followed
     * @param {Array}   context.selectors List of selectors to match
     * @param {Array}   context.whitelist List of rules to ignore
     * @param {Array}   context.excludedSubdomains List of subdomains to ignore
     * @param {Object}  context.result Object to append results to
     * @param {Object}  context.result.used E.g. {".foo": 1 }
     * @param {Object}  context.result.ignored E.g. {"@font-face": 1}
     * @param {Object}  queue Queue object, as returned from the Async library.
     */
    _processHtmlString: function(html, context, queue) {
        var uri = context.uri;
        var followLinks = context.followLinks;
        var selectors = context.selectors;
        var whitelist = context.whitelist;
        var result = context.result;
        var excludedSubdomains = context.excludedSubdomains;

        var document = cheerio.load(html);

        if (followLinks) { // look for links in document, add to queue
            var links = document("a");
            for (var i=0, l=links.length; i<l; i++) {
                var handleThis = true;

                var href = links[i].attribs.href.split("#")[0];

                // No href, or current URI with parameter, continue
                if (!href || href.indexOf("?") === 0) {
                    continue;
                }

                // If under excluded domain, skip
                for (var j=0; j<excludedSubdomains.length; j++) {
                    var excluded = excludedSubdomains[j];
                    if (href.indexOf(excluded) === 0 ||
                        url.parse(href).pathname.indexOf(excluded) === 0) {

                        handleThis = false;
                        break;
                    }
                }

                if (handleThis) {
                    this._addLinkToQueue(links[i].attribs.href, queue, uri, followLinks);
                }
            }
        }

        // Process current document
        return this._matchSelectorsInString(document, selectors, whitelist, result);
    },

    /**
     * Add link to queue (but only if it's on the same host as parentUri).
     *
     * @private
     * @param {String}  link Link to handle.
     * @param {Object}  queue Queue object, as returned from the Async library.
     * @param {String}  parentUri URL to document containing this link.
     * @param {Boolean} followLinks Should links in the document link refers to
     *                  be followed?
     */
    _addLinkToQueue: function(link, queue, parentUri, followLinks) {
        var host = url.parse(parentUri).host;
        if (0 === link.indexOf("http")) { // Absolute url
            if (url.parse(link).host === host) {
                queue.push({page: link, followLinks: followLinks});
            } else {
                // Skip, another domain
            }
        } else { // Relative url?
            if (parentUri) { // Yes
                link = url.resolve(parentUri, link);
                queue.push({page: link, followLinks: followLinks});
            } else { // What Is This Thing Called Link?
                console.log("Could not resolve " + link);
            }
        }
    },

    /**
     * Match selectors in a html string.
     *
     * @private
     * @param {String} htmlString HTML string
     * @param {Array}  selectors List of selectors to match
     * @param {Array}  whitelist List of selectors to ignore
     * @param {Object} result Object to append results to
     * @param {Object} result.used E.g. {".foo": 1 }
     * @param {Object} result.ignored E.g. {"@font-face": 1}
     */
    _matchSelectorsInString: function(htmlString, selectors, whitelist, result) {
        // Loop through selectors
        for (var k=0, l=selectors.length; k<l; k++) {
            var selector = selectors[k];

            // If current selector is whitelisted, skip.
            if (whitelist && -1 < whitelist.indexOf(selector)) {
                continue;
            }
            if (-1 < selector.indexOf("@")) {
                result.ignored[selector] = (result.ignored[selector] || 0) + 1;
                continue;
            }

            if (selector) {
                var oSelector = selector;

                // Add selector to index, if not already added
                if (undefined === result.used[oSelector]) {
                    result.used[oSelector] = 0;
                }

                // Remove pseudo part of selector
                selector = selector.split(":")[0];

                // Check if selector is used
                try {
                    if (htmlString(selector).length > 0) {
                        result.used[oSelector] =
                            result.used[oSelector]
                            + htmlString(selector).length;
                    }
                } catch (e) {
                    console.log("Problem with selector: "
                                + oSelector);
                }
            }
        }
        return;
    },

    /**
     * Match selectors in a html string.
     *
     * @private
     * @param {String} page URL, path or HTML as string.
     * @param {Array}  cookie Cookie to use for login, on the form
     *                 "sessionid=foo". Each uri in the html parameter will
     *                 be visited both with and without the cookie set.
     * @returns {Promise} HTML as string
     */
    _getHtmlAsString: function(page, cookie) {
        var deferred = Q.defer();
        var data;

        // Get page as raw html
        // If URI is given, fetch HTML
        //
        // Note: _addLinkToQueue adds host etc. to relative URLs when crawling.
        if (0 === page.indexOf("http")) { // From URI
            var uri = page;

            var headers = {};
            if (cookie) {
                headers = {
                    "Cookie": cookie,
                    "Referer": uri
                };
            }

            var options = { uri: uri,
                            headers: headers };

            require('request').get(options, function(error, res, data) {
                                       // TODO: Error checking, response status code, etc.
                                       deferred.resolve(data);
                                   });
        } else if (-1 === page.indexOf("<html>")) { // From file
            try {
                data = fs.readFileSync(page).toString();
            } catch (e) {
                console.warn("_getHtmlAsString() failed to read %s: %s", page, e.message);
            }
            deferred.resolve(data);
        } else { // From string
            deferred.resolve(page);
        }

        return deferred.promise;
    }
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2011 Vinay Pulim <vinay@milewise.com>
 * MIT Licensed
 */

'use strict';

var url = require('url');
var req = require('request');
var debug = require('debug')('node-soap');

var VERSION = require('../package.json').version;

/**
 * A class representing the http client
 * @param {Object} [options] Options object. It allows the customization of
 * `request` module
 *
 * @constructor
 */
function HttpClient(options) {
  options = options || {};
  this._request = options.request || req;
}

/**
 * Build the HTTP request (method, uri, headers, ...)
 * @param {String} rurl The resource url
 * @param {Object|String} data The payload
 * @param {Object} exheaders Extra http headers
 * @param {Object} exoptions Extra options
 * @returns {Object} The http request object for the `request` module
 */
HttpClient.prototype.buildRequest = function(rurl, data, exheaders, exoptions) {
  var curl = url.parse(rurl);
  var secure = curl.protocol === 'https:';
  var host = curl.hostname;
  var port = parseInt(curl.port, 10);
  var path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join('');
  var method = data ? 'POST' : 'GET';
  var headers = {
    'User-Agent': 'node-soap/' + VERSION,
    'Accept': 'text/html,application/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8',
    'Accept-Encoding': 'none',
    'Accept-Charset': 'utf-8',
    'Connection': 'close',
    'Host': host + (isNaN(port) ? '' : ':' + port)
  };
  var attr;

  if (typeof data === 'string') {
    headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
    headers['Content-Type'] = 'application/x-www-form-urlencoded';
  }

  exheaders = exheaders || {};
  for (attr in exheaders) {
    headers[attr] = exheaders[attr];
  }

  var options = {
    uri: curl,
    method: method,
    headers: headers,
    followAllRedirects: true
  };

  if (headers.Connection === 'keep-alive') {
    options.body = data;
  }

  exoptions = exoptions || {};
  for (attr in exoptions) {
    options[attr] = exoptions[attr];
  }
  debug('Http request: %j', options);
  return options;
};

/**
 * Handle the http response
 * @param {Object} The req object
 * @param {Object} res The res object
 * @param {Object} body The http body
 * @param {Object} The parsed body
 */
HttpClient.prototype.handleResponse = function(req, res, body) {
  debug('Http response body: %j', body);
  if (typeof body === 'string') {
    // Remove any extra characters that appear before or after the SOAP
    // envelope.
    var match = body.match(/(?:<\?[^?]*\?>[\s]*)?<([^:]*):Envelope([\S\s]*)<\/\1:Envelope>/i);
    if (match) {
      body = match[0];
    }
  }
  return body;
};

/**
 * Handle the http Attachment response
 * @param {Object} The req object
 * @param {Object} res The res object
 * @param {Object} body The http body
 * @param {Object} The attachment List
 */
HttpClient.prototype.handleAttachmentResponse = function(req, res, body) {
  debug('Http response body: %j', body);
  var arr = [];
  if (typeof body === 'string') {
     if (res && res.headers['content-type']) {
       var boundary = res.headers['content-type'].match(/boundary=\"([^\"]*)\"/i);

       if (boundary){
        var partSplitter = '--'+boundary[1];
        var parts = body.split(partSplitter);
        var attachments = parts.filter(function(part){
          return part.indexOf("attachment") > 1;
        })

        attachments.forEach(function(attachment){
          var attachement_split = attachment.split(/\r\n\r\n/g);

          var headers = {};
          attachement_split[0].split(/\r\n/g).forEach(function(header){
            var headerName = header.split(':')[0];
            var headerValue = header.split(':')[1];
            if (headerName){
              headerName= headerName.trim();
              if  ((headerValue !== undefined)&&(headerValue !== null)&&(headerValue !== '')){
              headerValue =headerValue.trim();
              }
              headers[headerName]=headerValue;
            }
          });

          var data = attachement_split[1];
          arr.push({headers:headers,data:data});
        })
      }
    }
  }
  return arr;
};

HttpClient.prototype.request = function(rurl, data, callback, exheaders, exoptions) {
  var self = this;
  var options = self.buildRequest(rurl, data, exheaders, exoptions);
  var headers = options.headers;
  var req = self._request(options, function(err, res, body) {
    if (err) {
      return callback(err);
    }
    var attachments = self.handleAttachmentResponse(req, res, body);
    body = self.handleResponse(req, res, body);
    callback(null, res, body,attachments);
  });
  if (headers.Connection !== 'keep-alive') {
    req.end(data);
  }
  return req;
};

module.exports = HttpClient;
<commit_msg>Update pour gérer les fin de lignes windows et unix<commit_after>/*
 * Copyright (c) 2011 Vinay Pulim <vinay@milewise.com>
 * MIT Licensed
 */

'use strict';

var url = require('url');
var req = require('request');
var debug = require('debug')('node-soap');

var VERSION = require('../package.json').version;

/**
 * A class representing the http client
 * @param {Object} [options] Options object. It allows the customization of
 * `request` module
 *
 * @constructor
 */
function HttpClient(options) {
  options = options || {};
  this._request = options.request || req;
}

/**
 * Build the HTTP request (method, uri, headers, ...)
 * @param {String} rurl The resource url
 * @param {Object|String} data The payload
 * @param {Object} exheaders Extra http headers
 * @param {Object} exoptions Extra options
 * @returns {Object} The http request object for the `request` module
 */
HttpClient.prototype.buildRequest = function(rurl, data, exheaders, exoptions) {
  var curl = url.parse(rurl);
  var secure = curl.protocol === 'https:';
  var host = curl.hostname;
  var port = parseInt(curl.port, 10);
  var path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join('');
  var method = data ? 'POST' : 'GET';
  var headers = {
    'User-Agent': 'node-soap/' + VERSION,
    'Accept': 'text/html,application/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8',
    'Accept-Encoding': 'none',
    'Accept-Charset': 'utf-8',
    'Connection': 'close',
    'Host': host + (isNaN(port) ? '' : ':' + port)
  };
  var attr;

  if (typeof data === 'string') {
    headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
    headers['Content-Type'] = 'application/x-www-form-urlencoded';
  }

  exheaders = exheaders || {};
  for (attr in exheaders) {
    headers[attr] = exheaders[attr];
  }

  var options = {
    uri: curl,
    method: method,
    headers: headers,
    followAllRedirects: true
  };

  if (headers.Connection === 'keep-alive') {
    options.body = data;
  }

  exoptions = exoptions || {};
  for (attr in exoptions) {
    options[attr] = exoptions[attr];
  }
  debug('Http request: %j', options);
  return options;
};

/**
 * Handle the http response
 * @param {Object} The req object
 * @param {Object} res The res object
 * @param {Object} body The http body
 * @param {Object} The parsed body
 */
HttpClient.prototype.handleResponse = function(req, res, body) {
  debug('Http response body: %j', body);
  if (typeof body === 'string') {
    // Remove any extra characters that appear before or after the SOAP
    // envelope.
    var match = body.match(/(?:<\?[^?]*\?>[\s]*)?<([^:]*):Envelope([\S\s]*)<\/\1:Envelope>/i);
    if (match) {
      body = match[0];
    }
  }
  return body;
};

/**
 * Handle the http Attachment response
 * @param {Object} The req object
 * @param {Object} res The res object
 * @param {Object} body The http body
 * @param {Object} The attachment List
 */
HttpClient.prototype.handleAttachmentResponse = function(req, res, body) {
  debug('Http response body: %j', body);
  var arr = [];
  if (typeof body === 'string') {
     if (res && res.headers['content-type']) {
       var boundary = res.headers['content-type'].match(/boundary=\"([^\"]*)\"/i);

       if (boundary){
        body = body.replace(new RegExp('\\r', 'g'),'');
        var partSplitter = '--'+boundary[1];
        var parts = body.split(partSplitter);
        var attachments = parts.filter(function(part){
          return part.indexOf("attachment") > 1;
        })
        attachments.forEach(function(attachment){
          var attachement_split = attachment.split(/\n\n/g);

          var headers = {};
          attachement_split[0].split(/\n/g).forEach(function(header){
            var headerName = header.split(':')[0];
            var headerValue = header.split(':')[1];
            if (headerName){
              headerName= headerName.trim();
              if  ((headerValue !== undefined)&&(headerValue !== null)&&(headerValue !== '')){
              headerValue =headerValue.trim();
              }
              headers[headerName]=headerValue;
            }
          });

          var data = attachement_split[1];
          arr.push({headers:headers,data:data});
        })
      }
    }
  }
  return arr;
};

HttpClient.prototype.request = function(rurl, data, callback, exheaders, exoptions) {
  var self = this;
  var options = self.buildRequest(rurl, data, exheaders, exoptions);
  var headers = options.headers;
  var req = self._request(options, function(err, res, body) {
    if (err) {
      return callback(err);
    }
    var attachments = self.handleAttachmentResponse(req, res, body);
    body = self.handleResponse(req, res, body);
    callback(null, res, body,attachments);
  });
  if (headers.Connection !== 'keep-alive') {
    req.end(data);
  }
  return req;
};

module.exports = HttpClient;
<|endoftext|>